From c3aa879c3c77233c401cf314d736bf20891991ba Mon Sep 17 00:00:00 2001 From: Andrey Tyrin Date: Mon, 9 Jan 2023 13:28:05 +0100 Subject: Move inheritors, params, see also and samples tabs to description for classlikes (#2749) --- .../documentables/DefaultPageCreator.kt | 385 ++-------------- .../documentables/DescriptionSections.kt | 346 +++++++++++++++ .../base/src/main/resources/dokka/styles/style.css | 7 +- .../content/exceptions/ContentForExceptions.kt | 434 ++++++++++++++++++ .../content/inheritors/ContentForInheritorsTest.kt | 494 +++++++++++++++++++++ .../kotlin/content/params/ContentForParamsTest.kt | 309 +++++++------ .../content/samples/ContentForSamplesTest.kt | 192 ++++++++ .../content/seealso/ContentForSeeAlsoTest.kt | 468 ++++++++++++++----- .../kotlin/linkableContent/LinkableContentTest.kt | 3 +- plugins/base/src/test/kotlin/utils/contentUtils.kt | 54 ++- .../src/test/resources/content/samples/samples.kt | 5 + 11 files changed, 2109 insertions(+), 588 deletions(-) create mode 100644 plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt create mode 100644 plugins/base/src/test/kotlin/content/exceptions/ContentForExceptions.kt create mode 100644 plugins/base/src/test/kotlin/content/inheritors/ContentForInheritorsTest.kt create mode 100644 plugins/base/src/test/kotlin/content/samples/ContentForSamplesTest.kt create mode 100644 plugins/base/src/test/resources/content/samples/samples.kt (limited to 'plugins/base/src') diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index 9d45f7a3..266d6e94 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -6,28 +6,19 @@ import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint import org.jetbrains.dokka.base.signatures.SignatureProvider import org.jetbrains.dokka.base.transformers.documentables.CallableExtensions import org.jetbrains.dokka.base.transformers.documentables.ClashingDriIdentifier -import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter import org.jetbrains.dokka.base.transformers.pages.tags.CustomTagContentProvider import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder.DocumentableContentBuilder import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.links.PointingToDeclaration import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.doc.* import org.jetbrains.dokka.model.properties.PropertyContainer import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.kotlin.utils.addToStdlib.safeAs import kotlin.reflect.KClass -import kotlin.reflect.full.isSubclassOf -internal const val KDOC_TAG_HEADER_LEVEL = 4 - -private typealias GroupedTags = Map, List>> - -private val specialTags: Set> = - setOf(Property::class, Description::class, Constructor::class, Param::class, See::class) +internal typealias GroupedTags = Map, List>> open class DefaultPageCreator( configuration: DokkaBaseConfiguration?, @@ -203,7 +194,6 @@ open class DefaultPageCreator( } } } - +contentForComments(m) block( "Packages", @@ -244,7 +234,6 @@ open class DefaultPageCreator( } } group(styles = setOf(ContentStyle.TabbedContent)) { - +contentForComments(p) +contentForScope(p, p.dri, p.sourceSets) } } @@ -254,25 +243,13 @@ open class DefaultPageCreator( sourceSets: Set ): ContentGroup { val types = scopes.flatMap { it.classlikes } + scopes.filterIsInstance().flatMap { it.typealiases } - val inheritors = scopes.fold(mutableMapOf>()) { acc, scope -> - val inheritorsForScope = - scope.safeAs>()?.let { it.extra[InheritorsInfo] }?.let { inheritors -> - inheritors.value.filter { it.value.isNotEmpty() } - }.orEmpty() - inheritorsForScope.forEach { (k, v) -> - acc.compute(k) { _, value -> value?.plus(v) ?: v } - } - acc - } - return contentForScope( @Suppress("UNCHECKED_CAST") (scopes as List).dri, sourceSets, types, scopes.flatMap { it.functions }, - scopes.flatMap { it.properties }, - inheritors + scopes.flatMap { it.properties } ) } @@ -285,12 +262,7 @@ open class DefaultPageCreator( s.classlikes, (s as? DPackage)?.typealiases ?: emptyList() ).flatten() - val inheritors = - s.safeAs>()?.let { it.extra[InheritorsInfo] }?.let { inheritors -> - inheritors.value.filter { it.value.isNotEmpty() } - }.orEmpty() - - return contentForScope(setOf(dri), sourceSets, types, s.functions, s.properties, inheritors) + return contentForScope(setOf(dri), sourceSets, types, s.functions, s.properties) } protected open fun contentForScope( @@ -298,8 +270,7 @@ open class DefaultPageCreator( sourceSets: Set, types: List, functions: List, - properties: List, - inheritors: SourceSetDependent> + properties: List ) = contentBuilder.contentFor(dri, sourceSets) { divergentBlock("Types", types, ContentKind.Classlikes, extra = mainExtra + SimpleAttr.header("Types")) if (separateInheritedMembers) { @@ -313,31 +284,6 @@ open class DefaultPageCreator( functionsBlock("Functions", functions) propertiesBlock("Properties", properties, sourceSets) } - if (inheritors.values.any()) { - header(2, "Inheritors") { } - +ContentTable( - header = listOf(contentBuilder.contentFor(mainDRI, mainSourcesetData) { - text("Name") - }), - children = inheritors.entries.flatMap { entry -> entry.value.map { Pair(entry.key, it) } } - .groupBy({ it.second }, { it.first }).map { (classlike, platforms) -> - val label = classlike.classNames?.substringAfterLast(".") ?: classlike.toString() - .also { logger.warn("No class name found for DRI $classlike") } - buildGroup( - setOf(classlike), - platforms.toSet(), - ContentKind.Inheritors, - extra = mainExtra + SymbolAnchorHint(label, ContentKind.Inheritors) - ) { - link(label, classlike) - } - }, - dci = DCI(dri, ContentKind.Inheritors), - sourceSets = sourceSets.toDisplaySourceSets(), - style = emptySet(), - extra = mainExtra + SimpleAttr.header("Inheritors") - ) - } } private fun Iterable.sorted() = @@ -367,9 +313,7 @@ open class DefaultPageCreator( } } } - group(styles = setOf(ContentStyle.TabbedContent), sourceSets = mainSourcesetData + extensions.sourceSets) { - +contentForComments(documentables) val csWithConstructor = classlikes.filterIsInstance() if (csWithConstructor.isNotEmpty() && documentables.shouldRenderConstructors()) { val constructorsToDocumented = csWithConstructor.flatMap { it.constructors } @@ -442,272 +386,28 @@ open class DefaultPageCreator( // and instantiated directly under normal circumstances, so constructors should not be rendered. private fun List.shouldRenderConstructors() = !this.any { it is DAnnotation } - @Suppress("UNCHECKED_CAST") - private inline fun GroupedTags.withTypeUnnamed(): SourceSetDependent = - (this[T::class] as List>?)?.toMap().orEmpty() - - @Suppress("UNCHECKED_CAST") - private inline fun GroupedTags.withTypeNamed(): Map> = - (this[T::class] as List>?) - ?.groupByTo(linkedMapOf()) { it.second.name } - ?.mapValues { (_, v) -> v.toMap() } - .orEmpty() - - private inline fun GroupedTags.isNotEmptyForTag(): Boolean = - this[T::class]?.isNotEmpty() ?: false - protected open fun contentForDescription( d: Documentable ): List { - val tags: GroupedTags = d.groupedTags - val platforms = d.sourceSets.toSet() - - return contentBuilder.contentFor(d, styles = setOf(TextStyle.Block)) { - deprecatedSectionContent(d, platforms) - - val descriptions = d.descriptions - if (descriptions.any { it.value.root.children.isNotEmpty() }) { - platforms.forEach { platform -> - descriptions[platform]?.also { - group(sourceSets = setOf(platform), styles = emptySet()) { - comment(it.root) - } - } - } - } - - val customTags = d.customTags - if (customTags.isNotEmpty()) { - platforms.forEach { platform -> - customTags.forEach { (_, sourceSetTag) -> - sourceSetTag[platform]?.let { tag -> - customTagContentProviders.filter { it.isApplicable(tag) }.forEach { provider -> - group(sourceSets = setOf(platform), styles = setOf(ContentStyle.KDocTag)) { - with(provider) { - contentForDescription(platform, tag) - } - } - } - } - } - } - } - - val unnamedTags = tags.filterNot { (k, _) -> k.isSubclassOf(NamedTagWrapper::class) || k in specialTags } - .values.flatten().groupBy { it.first }.mapValues { it.value.map { it.second } } - if (unnamedTags.isNotEmpty()) { - platforms.forEach { platform -> - unnamedTags[platform]?.let { tags -> - if (tags.isNotEmpty()) { - tags.groupBy { it::class }.forEach { (_, sameCategoryTags) -> - group(sourceSets = setOf(platform), styles = setOf(ContentStyle.KDocTag)) { - header( - level = KDOC_TAG_HEADER_LEVEL, - text = sameCategoryTags.first().toHeaderString(), - styles = setOf() - ) - sameCategoryTags.forEach { comment(it.root, styles = setOf()) } - } - } - } - } - } - } - }.children - } - - private fun Set.getPossibleFallbackSourcesets(sourceSet: DokkaSourceSet) = - this.filter { it.sourceSetID in sourceSet.dependentSourceSets } - - private fun Map.fallback(sourceSets: List): V? = - sourceSets.firstOrNull { it in this.keys }.let { this[it] } - - protected open fun contentForComments( - d: Documentable, - isPlatformHintedContent: Boolean = true - ) = contentForComments(d.dri, d.sourceSets, d.groupedTags, isPlatformHintedContent) - - protected open fun contentForComments( - d: List, - isPlatformHintedContent: Boolean = true - ) = contentForComments(d.first().dri, d.sourceSets, d.groupedTags, isPlatformHintedContent) - - protected open fun contentForComments( - dri: DRI, - sourceSets: Set, - tags: GroupedTags, - isPlatformHintedContent: Boolean = true - ): List { - - fun DocumentableContentBuilder.buildContent( - platforms: Set, - contentBuilder: DocumentableContentBuilder.() -> Unit - ) = if (isPlatformHintedContent) - sourceSetDependentHint( - sourceSets = platforms, - kind = ContentKind.SourceSetDependentHint, - block = contentBuilder - ) - else - contentBuilder() - - fun DocumentableContentBuilder.contentForParams() { - if (tags.isNotEmptyForTag()) { - val params = tags.withTypeNamed() - val availablePlatforms = params.values.flatMap { it.keys }.toSet() - - header(KDOC_TAG_HEADER_LEVEL, "Parameters", kind = ContentKind.Parameters, sourceSets = availablePlatforms) - group( - extra = mainExtra + SimpleAttr.header("Parameters"), - styles = setOf(ContentStyle.WithExtraAttributes), - sourceSets = availablePlatforms - ) { - buildContent(availablePlatforms) { - table(kind = ContentKind.Parameters, sourceSets = availablePlatforms) { - availablePlatforms.forEach { platform -> - val possibleFallbacks = sourceSets.getPossibleFallbackSourcesets(platform) - params.mapNotNull { (_, param) -> - (param[platform] ?: param.fallback(possibleFallbacks))?.let { - row(sourceSets = setOf(platform), kind = ContentKind.Parameters) { - text( - it.name, - kind = ContentKind.Parameters, - styles = mainStyles + setOf(ContentStyle.RowTitle, TextStyle.Underlined) - ) - if (it.isNotEmpty()) { - comment(it.root) - } - } - } - } - } - } - } - } - } - } + val sourceSets = d.sourceSets.toSet() + val tags = d.groupedTags - fun DocumentableContentBuilder.contentForSeeAlso() { - if (tags.isNotEmptyForTag()) { - val seeAlsoTags = tags.withTypeNamed() - val availablePlatforms = seeAlsoTags.values.flatMap { it.keys }.toSet() + return contentBuilder.contentFor(d) { + deprecatedSectionContent(d, sourceSets) - header(KDOC_TAG_HEADER_LEVEL, "See also", kind = ContentKind.Comment, sourceSets = availablePlatforms) - group( - extra = mainExtra + SimpleAttr.header("See also"), - styles = setOf(ContentStyle.WithExtraAttributes), - sourceSets = availablePlatforms - ) { - buildContent(availablePlatforms) { - table(kind = ContentKind.Sample) { - availablePlatforms.forEach { platform -> - val possibleFallbacks = sourceSets.getPossibleFallbackSourcesets(platform) - seeAlsoTags.forEach { (_, see) -> - (see[platform] ?: see.fallback(possibleFallbacks))?.let { seeTag -> - row( - sourceSets = setOf(platform), - kind = ContentKind.Comment, - styles = this@group.mainStyles, - ) { - seeTag.address?.let { dri -> - link( - text = seeTag.name.removePrefix("${dri.packageName}."), - address = dri, - kind = ContentKind.Comment, - styles = mainStyles + ContentStyle.RowTitle - ) - } ?: text( - text = seeTag.name, - kind = ContentKind.Comment, - styles = mainStyles + ContentStyle.RowTitle - ) - if (seeTag.isNotEmpty()) { - comment(seeTag.root) - } - } - } - } - } - } - } - } - } - } + descriptionSectionContent(d, sourceSets) + customTagSectionContent(d, sourceSets, customTagContentProviders) + unnamedTagSectionContent(d, sourceSets) { toHeaderString() } - fun DocumentableContentBuilder.contentForThrows() { - val throws = tags.withTypeNamed() - if (throws.isNotEmpty()) { - val availablePlatforms = throws.values.flatMap { it.keys }.toSet() - - header(KDOC_TAG_HEADER_LEVEL, "Throws", sourceSets = availablePlatforms) - buildContent(availablePlatforms) { - availablePlatforms.forEach { sourceset -> - table( - kind = ContentKind.Main, - sourceSets = setOf(sourceset), - extra = mainExtra + SimpleAttr.header("Throws") - ) { - throws.entries.forEach { entry -> - entry.value[sourceset]?.let { throws -> - row(sourceSets = setOf(sourceset)) { - group(styles = mainStyles + ContentStyle.RowTitle) { - throws.exceptionAddress?.let { - val className = it.takeIf { it.target is PointingToDeclaration }?.classNames - link(text = className ?: entry.key, address = it) - } ?: text(entry.key) - } - if (throws.isNotEmpty()) { - comment(throws.root) - } - } - } - } - } - } - } - } - } - - fun DocumentableContentBuilder.contentForSamples() { - val samples = tags.withTypeNamed() - if (samples.isNotEmpty()) { - val availablePlatforms = samples.values.flatMap { it.keys }.toSet() - header(KDOC_TAG_HEADER_LEVEL, "Samples", kind = ContentKind.Sample, sourceSets = availablePlatforms) - group( - extra = mainExtra + SimpleAttr.header("Samples"), - styles = emptySet(), - sourceSets = availablePlatforms - ) { - buildContent(availablePlatforms) { - availablePlatforms.map { platformData -> - val content = samples.filter { it.value.isEmpty() || platformData in it.value } - group( - sourceSets = setOf(platformData), - kind = ContentKind.Sample, - styles = setOf(TextStyle.Monospace, ContentStyle.RunnableSample) - ) { - content.forEach { - text(it.key) - } - } - } - } - } - } - } + paramsSectionContent(tags) + seeAlsoSectionContent(tags) + throwsSectionContent(tags) + samplesSectionContent(tags) - return contentBuilder.contentFor(dri, sourceSets) { - if (tags.isNotEmpty()) { - contentForSamples() - contentForSeeAlso() - contentForParams() - contentForThrows() - } + inheritorsSectionContent(d, logger) }.children } - private fun TagWrapper.isNotEmpty() = this.children.isNotEmpty() - protected open fun DocumentableContentBuilder.contentForBrief(documentable: Documentable) { documentable.sourceSets.forEach { sourceSet -> documentable.documentation[sourceSet]?.let { @@ -761,7 +461,6 @@ open class DefaultPageCreator( } after { +contentForDescription(d) - +contentForComments(d, isPlatformHintedContent = false) } } } @@ -890,35 +589,39 @@ open class DefaultPageCreator( } protected open fun TagWrapper.toHeaderString() = this.javaClass.toGenericString().split('.').last() +} - private val List.sourceSets: Set - get() = flatMap { it.sourceSets }.toSet() +internal val List.sourceSets: Set + get() = flatMap { it.sourceSets }.toSet() - private val List.dri: Set - get() = map { it.dri }.toSet() +internal val List.dri: Set + get() = map { it.dri }.toSet() - private val Documentable.groupedTags: GroupedTags - get() = documentation.flatMap { (pd, doc) -> - doc.children.map { pd to it }.toList() - }.groupBy { it.second::class } +internal val Documentable.groupedTags: GroupedTags + get() = documentation.flatMap { (pd, doc) -> + doc.children.map { pd to it }.toList() + }.groupBy { it.second::class } - private val List.groupedTags: GroupedTags - get() = this.flatMap { - it.documentation.flatMap { (pd, doc) -> - doc.children.map { pd to it }.toList() - } - }.groupBy { it.second::class } +internal val Documentable.descriptions: SourceSetDependent + get() = groupedTags.withTypeUnnamed() - private val Documentable.descriptions: SourceSetDependent - get() = groupedTags.withTypeUnnamed() +internal val Documentable.customTags: Map> + get() = groupedTags.withTypeNamed() - private val Documentable.customTags: Map> - get() = groupedTags.withTypeNamed() +private val Documentable.hasSeparatePage: Boolean + get() = this !is DTypeAlias - private val Documentable.hasSeparatePage: Boolean - get() = this !is DTypeAlias +@Suppress("UNCHECKED_CAST") +private fun T.nameAfterClash(): String = + ((this as? WithExtraProperties)?.extra?.get(DriClashAwareName)?.value ?: name).orEmpty() - @Suppress("UNCHECKED_CAST") - private fun T.nameAfterClash(): String = - ((this as? WithExtraProperties)?.extra?.get(DriClashAwareName)?.value ?: name).orEmpty() -} +@Suppress("UNCHECKED_CAST") +internal inline fun GroupedTags.withTypeUnnamed(): SourceSetDependent = + (this[T::class] as List>?)?.toMap().orEmpty() + +@Suppress("UNCHECKED_CAST") +internal inline fun GroupedTags.withTypeNamed(): Map> = + (this[T::class] as List>?) + ?.groupByTo(linkedMapOf()) { it.second.name } + ?.mapValues { (_, v) -> v.toMap() } + .orEmpty() \ No newline at end of file diff --git a/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt b/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt new file mode 100644 index 00000000..1720b1d0 --- /dev/null +++ b/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt @@ -0,0 +1,346 @@ +package org.jetbrains.dokka.base.translators.documentables + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo +import org.jetbrains.dokka.base.transformers.pages.tags.CustomTagContentProvider +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.PointingToDeclaration +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.SourceSetDependent +import org.jetbrains.dokka.model.WithScope +import org.jetbrains.dokka.model.doc.* +import org.jetbrains.dokka.model.orEmpty +import org.jetbrains.dokka.model.properties.WithExtraProperties +import org.jetbrains.dokka.pages.ContentKind +import org.jetbrains.dokka.pages.ContentStyle +import org.jetbrains.dokka.pages.SimpleAttr +import org.jetbrains.dokka.pages.TextStyle +import org.jetbrains.dokka.utilities.DokkaLogger +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import kotlin.reflect.KClass +import kotlin.reflect.full.isSubclassOf + +internal const val KDOC_TAG_HEADER_LEVEL = 4 + +private val unnamedTagsExceptions: Set> = + setOf(Property::class, Description::class, Constructor::class, Param::class, See::class) + +internal fun PageContentBuilder.DocumentableContentBuilder.descriptionSectionContent( + documentable: Documentable, + sourceSets: Set, +) { + val descriptions = documentable.descriptions + if (descriptions.any { it.value.root.children.isNotEmpty() }) { + sourceSets.forEach { sourceSet -> + descriptions[sourceSet]?.also { + group(sourceSets = setOf(sourceSet), styles = emptySet()) { + comment(it.root) + } + } + } + } +} + +/** + * Custom tags are tags which are not part of the [KDoc specification](https://kotlinlang.org/docs/kotlin-doc.html). For instance, a user-defined tag + * which is specific to the user's code base would be considered a custom tag. + * + * For details, see [CustomTagContentProvider] + */ +internal fun PageContentBuilder.DocumentableContentBuilder.customTagSectionContent( + documentable: Documentable, + sourceSets: Set, + customTagContentProviders: List, +) { + val customTags = documentable.customTags + if (customTags.isEmpty()) return + + sourceSets.forEach { sourceSet -> + customTags.forEach { (_, sourceSetTag) -> + sourceSetTag[sourceSet]?.let { tag -> + customTagContentProviders.filter { it.isApplicable(tag) }.forEach { provider -> + group(sourceSets = setOf(sourceSet), styles = setOf(ContentStyle.KDocTag)) { + with(provider) { + contentForDescription(sourceSet, tag) + } + } + } + } + } + } +} + +/** + * Tags in KDoc are used in form of "@tag name value". + * This function handles tags that have only value parameter without name. + * List of such tags: `@return`, `@author`, `@since`, `@receiver` + */ +internal fun PageContentBuilder.DocumentableContentBuilder.unnamedTagSectionContent( + documentable: Documentable, + sourceSets: Set, + toHeaderString: TagWrapper.() -> String, +) { + val unnamedTags = documentable.groupedTags + .filterNot { (k, _) -> k.isSubclassOf(NamedTagWrapper::class) || k in unnamedTagsExceptions } + .values.flatten().groupBy { it.first } + .mapValues { it.value.map { it.second } } + .takeIf { it.isNotEmpty() } ?: return + + sourceSets.forEach { sourceSet -> + unnamedTags[sourceSet]?.let { tags -> + if (tags.isNotEmpty()) { + tags.groupBy { it::class }.forEach { (_, sameCategoryTags) -> + group(sourceSets = setOf(sourceSet), styles = setOf(ContentStyle.KDocTag)) { + header( + level = KDOC_TAG_HEADER_LEVEL, + text = sameCategoryTags.first().toHeaderString(), + styles = setOf() + ) + sameCategoryTags.forEach { comment(it.root, styles = setOf()) } + } + } + } + } + } +} + + +internal fun PageContentBuilder.DocumentableContentBuilder.paramsSectionContent(tags: GroupedTags) { + val params = tags.withTypeNamed() + if (params.isEmpty()) return + + val availableSourceSets = params.availableSourceSets() + tableSectionContentBlock( + blockName = "Parameters", + kind = ContentKind.Parameters, + sourceSets = availableSourceSets + ) { + availableSourceSets.forEach { sourceSet -> + val possibleFallbacks = availableSourceSets.getPossibleFallback(sourceSet) + params.mapNotNull { (_, param) -> + (param[sourceSet] ?: param.fallback(possibleFallbacks))?.let { + row(sourceSets = setOf(sourceSet), kind = ContentKind.Parameters) { + text( + it.name, + kind = ContentKind.Parameters, + styles = mainStyles + setOf(ContentStyle.RowTitle, TextStyle.Underlined) + ) + if (it.isNotEmpty()) { + comment(it.root) + } + } + } + } + } + } +} + +internal fun PageContentBuilder.DocumentableContentBuilder.seeAlsoSectionContent(tags: GroupedTags) { + val seeAlsoTags = tags.withTypeNamed() + if (seeAlsoTags.isEmpty()) return + + val availableSourceSets = seeAlsoTags.availableSourceSets() + tableSectionContentBlock( + blockName = "See also", + kind = ContentKind.Comment, + sourceSets = availableSourceSets + ) { + availableSourceSets.forEach { sourceSet -> + val possibleFallbacks = availableSourceSets.getPossibleFallback(sourceSet) + seeAlsoTags.forEach { (_, see) -> + (see[sourceSet] ?: see.fallback(possibleFallbacks))?.let { seeTag -> + row( + sourceSets = setOf(sourceSet), + kind = ContentKind.Comment + ) { + seeTag.address?.let { dri -> + link( + text = seeTag.name.removePrefix("${dri.packageName}."), + address = dri, + kind = ContentKind.Comment, + styles = mainStyles + ContentStyle.RowTitle + ) + } ?: text( + text = seeTag.name, + kind = ContentKind.Comment, + styles = mainStyles + ContentStyle.RowTitle + ) + if (seeTag.isNotEmpty()) { + comment(seeTag.root) + } + } + } + } + } + } +} + +/** + * Used for multi-value tags (e.g. params) when values are missed on some platforms. + * It this case description is inherited from parent platform. + * E.g. if param hasn't description in JVM, the description is taken from common. + */ +private fun Set.getPossibleFallback(sourceSet: DokkaConfiguration.DokkaSourceSet) = + this.filter { it.sourceSetID in sourceSet.dependentSourceSets } + +private fun Map.fallback(sourceSets: List): V? = + sourceSets.firstOrNull { it in this.keys }.let { this[it] } + +internal fun PageContentBuilder.DocumentableContentBuilder.throwsSectionContent(tags: GroupedTags) { + val throwsTags = tags.withTypeNamed() + if (throwsTags.isEmpty()) return + + val availableSourceSets = throwsTags.availableSourceSets() + tableSectionContentBlock( + blockName = "Throws", + kind = ContentKind.Main, + sourceSets = availableSourceSets + ) { + throwsTags.forEach { (throwsName, throwsPerSourceSet) -> + throwsPerSourceSet.forEach { (sourceSet, throws) -> + row(sourceSets = setOf(sourceSet)) { + group(styles = mainStyles + ContentStyle.RowTitle) { + throws.exceptionAddress?.let { + val className = it.takeIf { it.target is PointingToDeclaration }?.classNames + link(text = className ?: throwsName, address = it) + } ?: text(throwsName) + } + if (throws.isNotEmpty()) { + comment(throws.root) + } + } + } + } + } +} + +private fun TagWrapper.isNotEmpty() = this.children.isNotEmpty() + +internal fun PageContentBuilder.DocumentableContentBuilder.samplesSectionContent(tags: GroupedTags) { + val samples = tags.withTypeNamed() + if (samples.isEmpty()) return + + val availableSourceSets = samples.availableSourceSets() + + header(KDOC_TAG_HEADER_LEVEL, "Samples", kind = ContentKind.Sample, sourceSets = availableSourceSets) + availableSourceSets.forEach { sourceSet -> + group( + sourceSets = setOf(sourceSet), + kind = ContentKind.Sample, + styles = setOf(TextStyle.Monospace, ContentStyle.RunnableSample), + extra = mainExtra + SimpleAttr.header("Samples") + ) { + samples.filter { it.value.isEmpty() || sourceSet in it.value } + .forEach { text(text = it.key, sourceSets = setOf(sourceSet)) } + } + } +} + +internal fun PageContentBuilder.DocumentableContentBuilder.inheritorsSectionContent( + documentable: Documentable, + logger: DokkaLogger, +) { + val inheritors = if (documentable is WithScope) documentable.inheritors() else return + if (inheritors.values.none()) return + + // split content section for the case: + // parent is in the shared source set (without expect-actual) and inheritor is in the platform code + if (documentable.isDefinedInSharedSourceSetOnly(inheritors.keys.toSet())) + sharedSourceSetOnlyInheritorsSectionContent(inheritors, logger) + else + multiplatformInheritorsSectionContent(documentable, inheritors, logger) +} + +private fun WithScope.inheritors() = safeAs>() + ?.let { it.extra[InheritorsInfo] } + ?.let { inheritors -> inheritors.value.filter { it.value.isNotEmpty() } } + .orEmpty() + +/** + * Detect that documentable is located only in the shared code without expect-actuals + * Value of `analysisPlatform` will be [Platform.common] in cases if a source set shared between 2 different platforms. + * But if it shared between 2 same platforms (e.g. jvm("awt") and jvm("android")) + * then the source set will be still marked as jvm platform. + * + * So, we also try to check if any of inheritors source sets depends on current documentable source set. + * that will mean that the source set is shared. + */ +private fun Documentable.isDefinedInSharedSourceSetOnly(inheritorsSourceSets: Set) = + sourceSets.size == 1 && + (sourceSets.first().analysisPlatform == Platform.common + || sourceSets.first().hasDependentSourceSet(inheritorsSourceSets)) + +private fun DokkaConfiguration.DokkaSourceSet.hasDependentSourceSet( + sourceSets: Set, +) = + sourceSets.any { sourceSet -> sourceSet.dependentSourceSets.any { it == this.sourceSetID } } + +private fun PageContentBuilder.DocumentableContentBuilder.multiplatformInheritorsSectionContent( + documentable: Documentable, + inheritors: Map>, + logger: DokkaLogger, +) { + // intersect is used for removing duplication in case of merged classlikes from different platforms + val availableSourceSets = inheritors.keys.toSet().intersect(documentable.sourceSets) + + tableSectionContentBlock( + blockName = "Inheritors", + kind = ContentKind.Inheritors, + sourceSets = availableSourceSets + ) { + availableSourceSets.forEach { sourceSet -> + inheritors[sourceSet]?.forEach { classlike: DRI -> + inheritorRow(classlike, logger, sourceSet) + } + } + } +} + +private fun PageContentBuilder.DocumentableContentBuilder.sharedSourceSetOnlyInheritorsSectionContent( + inheritors: Map>, + logger: DokkaLogger, +) { + val uniqueInheritors = inheritors.values.flatten().toSet() + tableSectionContentBlock( + blockName = "Inheritors", + kind = ContentKind.Inheritors, + ) { + uniqueInheritors.forEach { classlike -> + inheritorRow(classlike, logger) + } + } +} + +private fun PageContentBuilder.TableBuilder.inheritorRow( + classlike: DRI, logger: DokkaLogger, sourceSet: DokkaConfiguration.DokkaSourceSet? = null, +) = row { + link( + text = classlike.friendlyClassName() + ?: classlike.toString().also { logger.warn("No class name found for DRI $classlike") }, + address = classlike, + sourceSets = sourceSet?.let { setOf(it) } ?: mainSourcesetData + ) +} + +private fun PageContentBuilder.DocumentableContentBuilder.tableSectionContentBlock( + blockName: String, + kind: ContentKind, + sourceSets: Set = mainSourcesetData, + body: PageContentBuilder.TableBuilder.() -> Unit, +) { + header(KDOC_TAG_HEADER_LEVEL, text = blockName, kind = kind, sourceSets = sourceSets) + table( + kind = kind, + sourceSets = sourceSets, + extra = mainExtra + SimpleAttr.header(blockName) + ) { + body() + } +} + +private fun DRI.friendlyClassName() = classNames?.substringAfterLast(".") + +private fun Map>.availableSourceSets() = values.flatMap { it.keys }.toSet() + + diff --git a/plugins/base/src/main/resources/dokka/styles/style.css b/plugins/base/src/main/resources/dokka/styles/style.css index 431db3b8..5242a76d 100644 --- a/plugins/base/src/main/resources/dokka/styles/style.css +++ b/plugins/base/src/main/resources/dokka/styles/style.css @@ -946,9 +946,10 @@ td.content { display: none; } -/*Work around an issue: https://github.com/JetBrains/kotlin-playground/issues/91*/ -.platform-hinted[data-togglable="Samples"] > .content:not([data-active]), -.tabs-section-body > *[data-togglable="Samples"]:not([data-active]) { +/* Work around an issue: https://github.com/JetBrains/kotlin-playground/issues/91 +Applies for main description blocks with platform tabs. +Just in case of possible performance degradation it excluding tabs with briefs on classlike page */ +#content > div:not(.tabbedcontent) .sourceset-dependent-content:not([data-active]) { display: block !important; visibility: hidden; height: 0; diff --git a/plugins/base/src/test/kotlin/content/exceptions/ContentForExceptions.kt b/plugins/base/src/test/kotlin/content/exceptions/ContentForExceptions.kt new file mode 100644 index 00000000..f59ba529 --- /dev/null +++ b/plugins/base/src/test/kotlin/content/exceptions/ContentForExceptions.kt @@ -0,0 +1,434 @@ +package content.exceptions + +import matchers.content.* +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.PluginConfigurationImpl +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.DisplaySourceSet +import org.jetbrains.kotlin.utils.addIfNotNull +import org.junit.jupiter.api.Test +import utils.ParamAttributes +import utils.bareSignature +import utils.findTestType +import kotlin.test.assertEquals + +class ContentForExceptions : BaseAbstractTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + private val mppTestConfiguration = dokkaConfiguration { + moduleName = "example" + sourceSets { + val common = sourceSet { + name = "common" + displayName = "common" + analysisPlatform = "common" + sourceRoots = listOf("src/commonMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "jvm" + displayName = "jvm" + analysisPlatform = "jvm" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/jvmMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "linuxX64" + displayName = "linuxX64" + analysisPlatform = "native" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/linuxX64Main/kotlin/pageMerger/Test.kt") + } + } + pluginsConfigurations.addIfNotNull( + PluginConfigurationImpl( + DokkaBase::class.qualifiedName!!, + DokkaConfiguration.SerializationFormat.JSON, + """{ "mergeImplicitExpectActualDeclarations": true }""", + ) + ) + } + + @Test + fun `function with navigatable thrown exception`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |/** + |* @throws Exception + |*/ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + header(4) { +"Throws" } + table { + group { + group { + link { +"Exception" } + } + } + } + } + } + } + } + } + } + } + + @Test + fun `function with non-navigatable thrown exception`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |/** + |* @throws UnavailableException + |*/ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + header(4) { +"Throws" } + table { + group { + group { + +"UnavailableException" + } + } + } + } + } + } + } + } + } + } + + @Test + fun `multiplatofrm class with throws`() { + testInline( + """ + |/src/commonMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws CommonException + |*/ + |expect open class Parent + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws JvmException + |*/ + |actual open class Parent + | + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws LinuxException + |*/ + |actual open class Parent + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"expect open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + header(4) { +"Throws" } + table { + group { + group { + +"CommonException" + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "common", + this.sourceSets.first().name + ) + } + } + group { + group { + +"JvmException" + } + check { + sourceSets.assertSourceSet("jvm") + } + } + group { + group { + +"LinuxException" + } + check { + sourceSets.assertSourceSet("linuxX64") + } + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `multiplatofrm class with throws in few platforms`() { + testInline( + """ + |/src/commonMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws CommonException + |*/ + |expect open class Parent + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws JvmException + |*/ + |actual open class Parent + | + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |actual open class Parent + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"expect open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + header(4) { +"Throws" } + table { + group { + group { + +"CommonException" + } + check { + sourceSets.assertSourceSet("common") + } + } + group { + group { + +"JvmException" + } + check { + sourceSets.assertSourceSet("jvm") + } + } + check { + assertEquals(2, sourceSets.size) + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `throws in merged functions`() { + testInline( + """ + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws LinuxException + |*/ + |fun function() { + | println() + |} + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws JvmException + |*/ + |fun function() { + | println() + |} + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + ) + } + after { + header(4) { +"Throws" } + table { + group { + group { + +"JvmException" + } + } + check { + sourceSets.assertSourceSet("jvm") + } + } + } + check { + sourceSets.assertSourceSet("jvm") + } + } + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + ) + } + after { + header(4) { +"Throws" } + table { + group { + group { + +"LinuxException" + } + } + } + } + check { + sourceSets.assertSourceSet("linuxX64") + } + } + } + } + } + } + } +} + +private fun Set.assertSourceSet(expectedName: String) { + assertEquals(1, this.size) + assertEquals(expectedName, this.first().name) +} \ No newline at end of file diff --git a/plugins/base/src/test/kotlin/content/inheritors/ContentForInheritorsTest.kt b/plugins/base/src/test/kotlin/content/inheritors/ContentForInheritorsTest.kt new file mode 100644 index 00000000..09c927bd --- /dev/null +++ b/plugins/base/src/test/kotlin/content/inheritors/ContentForInheritorsTest.kt @@ -0,0 +1,494 @@ +package content.inheritors + +import matchers.content.* +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.PluginConfigurationImpl +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.kotlin.utils.addIfNotNull +import org.junit.jupiter.api.Test +import utils.classSignature +import utils.findTestType +import kotlin.test.assertEquals + +class ContentForInheritorsTest : BaseAbstractTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + private val mppTestConfiguration = dokkaConfiguration { + moduleName = "example" + sourceSets { + val common = sourceSet { + name = "common" + displayName = "common" + analysisPlatform = "common" + sourceRoots = listOf("src/commonMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "jvm" + displayName = "jvm" + analysisPlatform = "jvm" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/jvmMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "linuxX64" + displayName = "linuxX64" + analysisPlatform = "native" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/linuxX64Main/kotlin/pageMerger/Test.kt") + } + } + pluginsConfigurations.addIfNotNull( + PluginConfigurationImpl( + DokkaBase::class.qualifiedName!!, + DokkaConfiguration.SerializationFormat.JSON, + """{ "mergeImplicitExpectActualDeclarations": true }""", + ) + ) + } + + + //Case from skiko library + private val mppTestConfigurationSharedAsPlatform = dokkaConfiguration { + moduleName = "example" + sourceSets { + val common = sourceSet { + name = "common" + displayName = "common" + analysisPlatform = "common" + sourceRoots = listOf("src/commonMain/kotlin/pageMerger/Test.kt") + } + val jvm = sourceSet { + name = "jvm" + displayName = "jvm" + analysisPlatform = "jvm" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/jvmMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "android" + displayName = "android" + analysisPlatform = "jvm" + dependentSourceSets = setOf(jvm.value.sourceSetID) + sourceRoots = listOf("src/androidMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "awt" + displayName = "awt" + analysisPlatform = "jvm" + dependentSourceSets = setOf(jvm.value.sourceSetID) + sourceRoots = listOf("src/awtMain/kotlin/pageMerger/Test.kt") + } + + } + } + + @Test + fun `class with one inheritor has table in description`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |class Parent + | + |class Foo : Parent() + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Parent" + ) + header(4) { +"Inheritors" } + table { + group { + link { +"Foo" } + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `interface with few inheritors has table in description`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |interface Parent + | + |class Foo : Parent() + |class Bar : Parent() + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"interface " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"Foo" } + } + group { + link { +"Bar" } + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `inherit from one of multiplatoforms actuals`() { + testInline( + """ + |/src/commonMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |expect open class Parent + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |actual open class Parent + | + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |actual open class Parent + |class Child: Parent() + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"expect open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"Child" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "linuxX64", + this.sourceSets.first().name + ) + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `inherit from class in common code`() { + testInline( + """ + |/src/commonMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |open class Parent + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class Child : Parent() + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"open class " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"Child" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "common", + this.sourceSets.first().name + ) + } + } + } + } + skipAllNotMatching() + } + } + } + } + + + @Test + fun `inheritors from merged classes`() { + testInline( + """ + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |open class Parent + |class LChild : Parent() + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |open class Parent + |class JChild : Parent() + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"open class " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"JChild" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "jvm", + this.sourceSets.first().name + ) + } + } + group { + +"open class " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"LChild" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "linuxX64", + this.sourceSets.first().name + ) + } + } + } + } + skipAllNotMatching() + } + } + } + } + + + @Test + fun `merged inheritors from merged classes`() { + testInline( + """ + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |open class Parent + |class Child : Parent() + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |open class Parent + |class Child : Parent() + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"open class " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"Child" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "jvm", + this.sourceSets.first().name + ) + } + } + group { + +"open class " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"Child" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "linuxX64", + this.sourceSets.first().name + ) + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `parent in shared source set that analyse as platform`() { + testInline( + """ + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |interface Parent + | + |/src/androidMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class Child : Parent + | + |/src/awtMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class AwtChild : Parent + |class Child : Parent + | + """.trimMargin(), + mppTestConfigurationSharedAsPlatform + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"interface " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"Child" } + } + group { + link { +"AwtChild" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "jvm", + this.sourceSets.first().name + ) + } + } + } + } + skipAllNotMatching() + } + } + } + } +} \ No newline at end of file diff --git a/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt b/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt index 3531f148..742c801f 100644 --- a/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt +++ b/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt @@ -2,12 +2,12 @@ package content.params import matchers.content.* import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest import org.jetbrains.dokka.model.DFunction import org.jetbrains.dokka.model.dfs import org.jetbrains.dokka.model.doc.DocumentationNode import org.jetbrains.dokka.model.doc.Param import org.jetbrains.dokka.model.doc.Text -import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest import org.jetbrains.dokka.pages.* import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.junit.jupiter.api.Test @@ -37,8 +37,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -76,8 +75,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -122,8 +120,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -175,8 +172,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val classPage = - module.children.single { it.name == "sample" }.children.single { it.name == "DocGenProcessor" } as ContentPage + val classPage = module.findTestType("sample", "DocGenProcessor") classPage.content.assertNode { group { header { +"DocGenProcessor" } @@ -219,8 +215,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val classPage = - module.children.single { it.name == "sample" }.children.single { it.name == "DocGenProcessor" } as ContentPage + val classPage = module.findTestType("sample", "DocGenProcessor") classPage.content.assertNode { group { header { +"DocGenProcessor" } @@ -274,7 +269,7 @@ class ContentForParamsTest : BaseAbstractTest() { ) { pagesTransformationStage = { module -> val classPage = - module.children.single { it.name == "sample" }.children.single { it.name == "DocGenProcessor" } as ContentPage + module.findTestType("sample", "DocGenProcessor") classPage.content.assertNode { group { header { +"DocGenProcessor" } @@ -327,7 +322,7 @@ class ContentForParamsTest : BaseAbstractTest() { ) { pagesTransformationStage = { module -> val classPage = - module.children.single { it.name == "sample" }.children.single { it.name == "DocGenProcessor" } as ContentPage + module.findTestType("sample", "DocGenProcessor") classPage.content.assertNode { group { header { +"DocGenProcessor" } @@ -375,7 +370,7 @@ class ContentForParamsTest : BaseAbstractTest() { ) { pagesTransformationStage = { module -> val classPage = - module.children.single { it.name == "sample" }.children.single { it.name == "DocGenProcessor" } as ContentPage + module.findTestType("sample", "DocGenProcessor") classPage.content.assertNode { group { header { +"DocGenProcessor" } @@ -436,7 +431,10 @@ class ContentForParamsTest : BaseAbstractTest() { ) { pagesTransformationStage = { module -> val functionPage = - module.children.single { it.name == "sample" }.children.single { it.name == "DocGenProcessor" }.children.single { it.name == "sample" } as ContentPage + module.findTestType( + "sample", + "DocGenProcessor" + ).children.single { it.name == "sample" } as ContentPage functionPage.content.assertNode { group { header(1) { +"sample" } @@ -492,8 +490,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val functionPage = - module.children.single { it.name == "sample" }.children.single { it.name == "sample" } as ContentPage + val functionPage = module.findTestType("sample", "sample") functionPage.content.assertNode { group { header(1) { +"sample" } @@ -564,7 +561,7 @@ class ContentForParamsTest : BaseAbstractTest() { ) { pagesTransformationStage = { module -> val functionPage = - module.children.single { it.name == "sample" }.children.single { it.name == "sample" } as ContentPage + module.findTestType("sample", "sample") functionPage.content.assertNode { group { header(1) { +"sample" } @@ -616,7 +613,10 @@ class ContentForParamsTest : BaseAbstractTest() { ) { pagesTransformationStage = { module -> val functionPage = - module.children.single { it.name == "sample" }.children.single { it.name == "DocGenProcessor" }.children.single { it.name == "sample" } as ContentPage + module.findTestType( + "sample", + "DocGenProcessor" + ).children.single { it.name == "sample" } as ContentPage functionPage.content.assertNode { group { header(1) { +"sample" } @@ -693,7 +693,7 @@ class ContentForParamsTest : BaseAbstractTest() { ) { pagesTransformationStage = { module -> val classPage = - module.children.single { it.name == "sample" }.children.single { it.name == "DocGenProcessor" } as ContentPage + module.findTestType("sample", "DocGenProcessor") classPage.content.assertNode { group { header { +"DocGenProcessor" } @@ -742,7 +742,10 @@ class ContentForParamsTest : BaseAbstractTest() { ) { pagesTransformationStage = { module -> val functionPage = - module.children.single { it.name == "sample" }.children.single { it.name == "DocGenProcessor" }.children.single { it.name == "sample" } as ContentPage + module.findTestType( + "sample", + "DocGenProcessor" + ).children.single { it.name == "sample" } as ContentPage functionPage.content.assertNode { group { header(1) { +"sample" } @@ -763,14 +766,12 @@ class ContentForParamsTest : BaseAbstractTest() { } } header(4) { +"Parameters" } - group { - table { - group { - +"testParam" - comment { - +"Sample description for test param that has a type of " - link { +"String" } - } + table { + group { + +"testParam" + comment { + +"Sample description for test param that has a type of " + link { +"String" } } } } @@ -801,8 +802,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val functionPage = - module.children.single { it.name == "sample" }.children.single { it.name == "sample" } as ContentPage + val functionPage = module.findTestType("sample", "sample") functionPage.content.assertNode { group { header(1) { +"sample" } @@ -855,8 +855,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val classPage = - module.children.single { it.name == "sample" }.children.single { it.name == "DocGenProcessor" } as ContentPage + val classPage = module.findTestType("sample", "DocGenProcessor") classPage.content.assertNode { group { header { +"DocGenProcessor" } @@ -921,7 +920,7 @@ class ContentForParamsTest : BaseAbstractTest() { ) { pagesTransformationStage = { module -> val classPage = - module.children.single { it.name == "sample" }.children.single { it.name == "DocGenProcessor" } as ContentPage + module.findTestType("sample", "DocGenProcessor") classPage.content.assertNode { group { header { +"DocGenProcessor" } @@ -982,8 +981,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -1029,8 +1027,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -1051,21 +1048,69 @@ class ContentForParamsTest : BaseAbstractTest() { after { group { pWrapped("comment to function") } header(4) { +"Parameters" } - group { - table { - group { - +"abc" - check { - val textStyles = children.single { it is ContentText }.style - assertContains(textStyles, TextStyle.Underlined) - } - group { group { +"comment to param" } } + table { + group { + +"abc" + check { + val textStyles = children.single { it is ContentText }.style + assertContains(textStyles, TextStyle.Underlined) } + group { group { +"comment to param" } } + } + } + } + } + } + } + } + } + } + + @Test + fun `single parameter in class`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to class + | * @param abc comment to param + | */ + |class Foo(abc: String) + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Foo") + println(page.content) + page.content.assertNode { + group { + header(1) { +"Foo" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Foo", + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + group { + pWrapped("comment to class") + } + header(4) { +"Parameters" } + table { + group { + +"abc" + check { + val textStyles = children.single { it is ContentText }.style + assertContains(textStyles, TextStyle.Underlined) } + group { group { +"comment to param" } } } } } } + skipAllNotMatching() } } } @@ -1089,8 +1134,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -1108,32 +1152,30 @@ class ContentForParamsTest : BaseAbstractTest() { after { group { group { group { +"comment to function" } } } header(4) { +"Parameters" } - group { - table { - group { - +"first" - check { - val textStyles = children.single { it is ContentText }.style - assertContains(textStyles, TextStyle.Underlined) - } - group { group { +"comment to first param" } } + table { + group { + +"first" + check { + val textStyles = children.single { it is ContentText }.style + assertContains(textStyles, TextStyle.Underlined) } - group { - +"second" - check { - val textStyles = children.single { it is ContentText }.style - assertContains(textStyles, TextStyle.Underlined) - } - group { group { +"comment to second param" } } + group { group { +"comment to first param" } } + } + group { + +"second" + check { + val textStyles = children.single { it is ContentText }.style + assertContains(textStyles, TextStyle.Underlined) } - group { - +"third" - check { - val textStyles = children.single { it is ContentText }.style - assertContains(textStyles, TextStyle.Underlined) - } - group { group { +"comment to third param" } } + group { group { +"comment to second param" } } + } + group { + +"third" + check { + val textStyles = children.single { it is ContentText }.style + assertContains(textStyles, TextStyle.Underlined) } + group { group { +"comment to third param" } } } } } @@ -1163,8 +1205,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -1182,22 +1223,21 @@ class ContentForParamsTest : BaseAbstractTest() { after { group { group { group { +"comment to function" } } } header(4) { +"Parameters" } - group { - table { - group { - +"c" - group { group { +"comment to c param" } } - } - group { - +"b" - group { group { +"comment to b param" } } - } - group { - +"a" - group { group { +"comment to a param" } } - } + table { + group { + +"c" + group { group { +"comment to c param" } } + } + group { + +"b" + group { group { +"comment to b param" } } + } + group { + +"a" + group { group { +"comment to a param" } } } } + } } } @@ -1223,8 +1263,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -1241,20 +1280,18 @@ class ContentForParamsTest : BaseAbstractTest() { } after { header(4) { +"Parameters" } - group { - table { - group { - +"first" - group { group { +"comment to first param" } } - } - group { - +"second" - group { group { +"comment to second param" } } - } - group { - +"third" - group { group { +"comment to third param" } } - } + table { + group { + +"first" + group { group { +"comment to first param" } } + } + group { + +"second" + group { group { +"comment to second param" } } + } + group { + +"third" + group { group { +"comment to third param" } } } } } @@ -1282,8 +1319,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -1309,14 +1345,13 @@ class ContentForParamsTest : BaseAbstractTest() { pWrapped("comment to receiver") } header(4) { +"Parameters" } - group { - table { - group { - +"abc" - group { group { +"comment to param" } } - } + table { + group { + +"abc" + group { group { +"comment to param" } } } } + } } } @@ -1342,8 +1377,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -1361,16 +1395,14 @@ class ContentForParamsTest : BaseAbstractTest() { after { group { group { group { +"comment to function" } } } header(4) { +"Parameters" } - group { - table { - group { - +"first" - group { group { +"comment to first param" } } - } - group { - +"third" - group { group { +"comment to third param" } } - } + table { + group { + +"first" + group { group { +"comment to first param" } } + } + group { + +"third" + group { group { +"comment to third param" } } } } } @@ -1401,8 +1433,7 @@ class ContentForParamsTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -1423,20 +1454,18 @@ class ContentForParamsTest : BaseAbstractTest() { unnamedTag("Since") { comment { +"0.11" } } header(4) { +"Parameters" } - group { - table { - group { - +"first" - group { group { +"comment to first param" } } - } - group { - +"second" - group { group { +"comment to second param" } } - } - group { - +"third" - group { group { +"comment to third param" } } - } + table { + group { + +"first" + group { group { +"comment to first param" } } + } + group { + +"second" + group { group { +"comment to second param" } } + } + group { + +"third" + group { group { +"comment to third param" } } } } } diff --git a/plugins/base/src/test/kotlin/content/samples/ContentForSamplesTest.kt b/plugins/base/src/test/kotlin/content/samples/ContentForSamplesTest.kt new file mode 100644 index 00000000..37009e46 --- /dev/null +++ b/plugins/base/src/test/kotlin/content/samples/ContentForSamplesTest.kt @@ -0,0 +1,192 @@ +package content.samples + +import matchers.content.* +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.DisplaySourceSet +import org.junit.jupiter.api.Test +import utils.classSignature +import utils.findTestType +import java.nio.file.Paths +import kotlin.test.assertEquals + +class ContentForSamplesTest : BaseAbstractTest() { + private val testDataDir = getTestDataDir("content/samples").toAbsolutePath() + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + samples = listOf( + Paths.get("$testDataDir/samples.kt").toString(), + ) + } + } + } + + private val mppTestConfiguration = dokkaConfiguration { + moduleName = "example" + sourceSets { + val common = sourceSet { + name = "common" + displayName = "common" + analysisPlatform = "common" + sourceRoots = listOf("src/commonMain/kotlin/pageMerger/Test.kt") + samples = listOf( + Paths.get("$testDataDir/samples.kt").toString(), + ) + } + sourceSet { + name = "jvm" + displayName = "jvm" + analysisPlatform = "jvm" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/jvmMain/kotlin/pageMerger/Test.kt") + samples = listOf( + Paths.get("$testDataDir/samples.kt").toString(), + ) + } + sourceSet { + name = "linuxX64" + displayName = "linuxX64" + analysisPlatform = "native" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/linuxX64Main/kotlin/pageMerger/Test.kt") + samples = listOf( + Paths.get("$testDataDir/samples.kt").toString(), + ) + } + } + } + + @Test + fun `samples block is rendered in the description`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + | /** + | * @sample [test.sampleForClassDescription] + | */ + |class Foo + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Foo") + page.content.assertNode { + group { + header(1) { +"Foo" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Foo" + ) + header(4) { +"Samples" } + group { + codeBlock { + +"""| + |fun main() { + | //sampleStart + | print("Hello") + | //sampleEnd + |}""".trimMargin() + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `multiplatofrm class with samples in few platforms`() { + testInline( + """ + |/src/commonMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @sample [test.sampleForClassDescription] + |*/ + |expect open class Parent + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @sample unresolved + |*/ + |actual open class Parent + | + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |actual open class Parent + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"expect open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + header(4) { +"Samples" } + group { + codeBlock { + +"""| + |fun main() { + | //sampleStart + | print("Hello") + | //sampleEnd + |}""".trimMargin() + } + check { + sourceSets.assertSourceSet("common") + } + } + group { + +"unresolved" + check { + sourceSets.assertSourceSet("jvm") + } + } + } + } + skipAllNotMatching() + } + } + } + } +} + + +private fun Set.assertSourceSet(expectedName: String) { + assertEquals(1, this.size) + assertEquals(expectedName, this.first().name) +} \ No newline at end of file diff --git a/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt b/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt index 5dee546f..79c1e1ad 100644 --- a/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt +++ b/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt @@ -1,14 +1,9 @@ package content.seealso import matchers.content.* -import org.jetbrains.dokka.pages.ContentDRILink -import org.jetbrains.dokka.pages.ContentPage import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest -import org.jetbrains.dokka.links.Callable -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.links.JavaClassReference -import org.jetbrains.dokka.model.doc.See -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import org.jetbrains.dokka.model.DisplaySourceSet +import org.jetbrains.dokka.pages.ContentDRILink import org.junit.jupiter.api.Test import utils.* import kotlin.test.assertEquals @@ -23,6 +18,32 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { } } + private val mppTestConfiguration = dokkaConfiguration { + moduleName = "example" + sourceSets { + val common = sourceSet { + name = "common" + displayName = "common" + analysisPlatform = "common" + sourceRoots = listOf("src/commonMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "jvm" + displayName = "jvm" + analysisPlatform = "jvm" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/jvmMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "linuxX64" + displayName = "linuxX64" + analysisPlatform = "native" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/linuxX64Main/kotlin/pageMerger/Test.kt") + } + } + } + @Test fun `undocumented function`() { testInline( @@ -36,8 +57,7 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -77,8 +97,7 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -98,12 +117,10 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { } after { header(4) { +"See also" } - group { - table { - group { - //DRI should be "test//abc/#/-1/" - link { +"abc" } - } + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"abc" } } } } @@ -114,6 +131,128 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { } } + @Test + fun `undocumented seealso without reference for class`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see abc + | */ + |class Foo() + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Foo") + println(page.content) + page.content.assertNode { + group { + header(1) { +"Foo" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Foo" + ) + header(4) { +"See also" } + table { + group { + +"abc" + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `undocumented seealso with reference to parameter for class`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see abc + | */ + |class Foo(abc: String) + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Foo") + println(page.content) + page.content.assertNode { + group { + header(1) { +"Foo" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Foo", + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + header(4) { +"See also" } + table { + group { + +"abc" + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `undocumented seealso with reference to property for class`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see abc + | */ + |class Foo(val abc: String) + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Foo") + println(page.content) + page.content.assertNode { + group { + header(1) { +"Foo" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Foo", + "val abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + header(4) { +"See also" } + table { + group { + link { +"Foo.abc" } + } + } + } + } + skipAllNotMatching() + } + } + } + } + @Test fun `documented seealso`() { testInline( @@ -129,8 +268,7 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -150,14 +288,12 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { } after { header(4) { +"See also" } - group { - table { + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"abc" } group { - //DRI should be "test//abc/#/-1/" - link { +"abc" } - group { - group { +"Comment to abc" } - } + group { +"Comment to abc" } } } } @@ -169,6 +305,50 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { } } + @Test + fun `documented seealso with reference to property for class`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see abc Comment to abc + | */ + |class Foo(val abc: String) + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Foo") + println(page.content) + page.content.assertNode { + group { + header(1) { +"Foo" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Foo", + "val abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + header(4) { +"See also" } + table { + group { + link { +"Foo.abc" } + group { + group { +"Comment to abc" } + } + } + } + } + } + skipAllNotMatching() + } + } + } + } + @Test fun `should use fully qualified name for unresolved link`() { testInline( @@ -184,8 +364,7 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -205,13 +384,11 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { } after { header(4) { +"See also" } - group { - table { + table { + group { + +"com.example.NonExistingClass" group { - +"com.example.NonExistingClass" - group { - group { +"description for non-existing" } - } + group { +"description for non-existing" } } } } @@ -238,8 +415,7 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -259,21 +435,20 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { } after { header(4) { +"See also" } - group { - table { - group { - link { - check { - assertEquals( - "kotlin.collections/Collection///PointingToDeclaration/", - (this as ContentDRILink).address.toString() - ) - } - +"Collection" + table { + group { + link { + check { + assertEquals( + "kotlin.collections/Collection///PointingToDeclaration/", + (this as ContentDRILink).address.toString() + ) } + +"Collection" } } } + } } } @@ -297,8 +472,7 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -318,16 +492,15 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { } after { header(4) { +"See also" } - group { - table { + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"Collection" } group { - //DRI should be "test//abc/#/-1/" - link { +"Collection" } - group { - group { +"Comment to stdliblink" } - } + group { +"Comment to stdliblink" } } } + } } } @@ -355,8 +528,7 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -380,17 +552,16 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { unnamedTag("Since") { comment { +"0.11" } } header(4) { +"See also" } - group { - table { + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"Collection" } group { - //DRI should be "test//abc/#/-1/" - link { +"Collection" } - group { - group { +"Comment to stdliblink" } - } + group { +"Comment to stdliblink" } } } } + } } } @@ -415,8 +586,7 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -436,17 +606,16 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { } after { header(4) { +"See also" } - group { - table { + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"abc" } group { - //DRI should be "test//abc/#/-1/" - link { +"abc" } - group { - group { +"Comment to abc2" } - } + group { +"Comment to abc2" } } } } + } } } @@ -471,8 +640,7 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { """.trimIndent(), testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("test", "function") page.content.assertNode { group { header(1) { +"function" } @@ -492,21 +660,19 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { } after { header(4) { +"See also" } - group { - table { + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"abc" } group { - //DRI should be "test//abc/#/-1/" - link { +"abc" } - group { - group { +"Comment to abc1" } - } - } - group { - //DRI should be "test//abc/#/-1/" - link { +"Collection" } - group { group { +"Comment to collection" } } + group { +"Comment to abc1" } } } + group { + //DRI should be "test//abc/#/-1/" + link { +"Collection" } + group { group { +"Comment to collection" } } + } } } } @@ -543,8 +709,7 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { testConfiguration ) { pagesTransformationStage = { module -> - val page = module.children.single { it.name == "com.example" } - .children.single { it.name == "function" } as ContentPage + val page = module.findTestType("com.example", "function") page.content.assertNode { group { @@ -564,19 +729,17 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { } after { header(4) { +"See also" } - group { - table { + table { + group { + link { +"CollectionExtensions.property" } group { - link { +"CollectionExtensions.property" } - group { - group { +"static property" } - } + group { +"static property" } } + } + group { + link { +"CollectionExtensions.emptyList" } group { - link { +"CollectionExtensions.emptyList" } - group { - group { +"static emptyList" } - } + group { +"static emptyList" } } } } @@ -587,4 +750,109 @@ class ContentForSeeAlsoTest : BaseAbstractTest() { } } } + + @Test + fun `multiplatform class with seealso in few platforms`() { + testInline( + """ + |/src/commonMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @see Unit + |*/ + |expect open class Parent + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |val x = 0 + |/** + |* @see x resolved + |* @see y unresolved + |*/ + |actual open class Parent + | + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |actual open class Parent + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"expect open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + header(4) { + +"See also" + check { + assertEquals(2, sourceSets.size) + } + } + table { + group { + link { +"Unit" } + check { + sourceSets.assertSourceSet("common") + } + } + group { + link { +"Unit" } + check { + sourceSets.assertSourceSet("jvm") + } + } + group { + link { +"x" } + group { group { +"resolved" } } + check { + sourceSets.assertSourceSet("jvm") + } + } + group { + +"y" + group { group { +"unresolved" } } + check { + sourceSets.assertSourceSet("jvm") + } + } + + check { + assertEquals(2, sourceSets.size) + } + } + } + } + skipAllNotMatching() + } + } + } + } } + +private fun Set.assertSourceSet(expectedName: String) { + assertEquals(1, this.size) + assertEquals(expectedName, this.first().name) +} \ No newline at end of file diff --git a/plugins/base/src/test/kotlin/linkableContent/LinkableContentTest.kt b/plugins/base/src/test/kotlin/linkableContent/LinkableContentTest.kt index fadc8e83..7dfe1e1d 100644 --- a/plugins/base/src/test/kotlin/linkableContent/LinkableContentTest.kt +++ b/plugins/base/src/test/kotlin/linkableContent/LinkableContentTest.kt @@ -1,13 +1,13 @@ package linkableContent import org.jetbrains.dokka.SourceLinkDefinitionImpl +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest import org.jetbrains.dokka.base.transformers.pages.samples.DefaultSamplesTransformer import org.jetbrains.dokka.base.transformers.pages.sourcelinks.SourceLinksTransformer import org.jetbrains.dokka.model.WithGenerics import org.jetbrains.dokka.model.dfs import org.jetbrains.dokka.model.doc.Text import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest import org.jetbrains.kotlin.utils.addToStdlib.cast import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.junit.jupiter.api.Assertions @@ -200,7 +200,6 @@ class LinkableContentTest : BaseAbstractTest() { .cast().children.single() .cast().after .cast().children.last() - .cast().children.last() .cast().children.single() .cast().children.single().cast().text Assertions.assertEquals( diff --git a/plugins/base/src/test/kotlin/utils/contentUtils.kt b/plugins/base/src/test/kotlin/utils/contentUtils.kt index 9f024c5b..d38af3f4 100644 --- a/plugins/base/src/test/kotlin/utils/contentUtils.kt +++ b/plugins/base/src/test/kotlin/utils/contentUtils.kt @@ -1,9 +1,9 @@ package utils import matchers.content.* -import org.jetbrains.dokka.model.* import org.jetbrains.dokka.pages.ContentGroup -import kotlin.text.Typography.nbsp +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.RootPageNode //TODO: Try to unify those functions after update to 1.4 fun ContentMatcherBuilder<*>.functionSignature( @@ -68,6 +68,53 @@ fun ContentMatcherBuilder<*>.bareSignature( } } +fun ContentMatcherBuilder<*>.classSignature( + annotations: Map>, + visibility: String, + modifier: String, + keywords: Set, + name: String, + vararg params: Pair, + parent: String? = null +) = group { + annotations.entries.forEach { + group { + unwrapAnnotation(it) + } + } + if (visibility.isNotBlank()) +"$visibility " + if (modifier.isNotBlank()) +"$modifier " + +("${keywords.joinToString("") { "$it " }}class ") + link { +name } + if (params.isNotEmpty()) { + +"(" + group { + params.forEachIndexed { id, (n, t) -> + group { + t.annotations.forEach { + unwrapAnnotation(it) + } + t.keywords.forEach { + +it + } + + +"$n: " + group { link { +(t.type) } } + if (id != params.lastIndex) + +", " + } + } + } + +")" + } + if (parent != null) { + +(" : ") + link { + +(parent) + } + } +} + fun ContentMatcherBuilder<*>.functionSignatureWithReceiver( annotations: Map>, visibility: String?, @@ -272,3 +319,6 @@ data class ParamAttributes( val keywords: Set, val type: String ) + +fun RootPageNode.findTestType(packageName: String, name: String) = + children.single { it.name == packageName }.children.single { it.name == name } as ContentPage \ No newline at end of file diff --git a/plugins/base/src/test/resources/content/samples/samples.kt b/plugins/base/src/test/resources/content/samples/samples.kt new file mode 100644 index 00000000..dcd38f40 --- /dev/null +++ b/plugins/base/src/test/resources/content/samples/samples.kt @@ -0,0 +1,5 @@ +package test + +fun sampleForClassDescription() { + print("Hello") +} \ No newline at end of file -- cgit