Skip to content

Commit

Permalink
Generate dedicated pages for typealiases (#3051)
Browse files Browse the repository at this point in the history
  • Loading branch information
vmishenev committed Jun 30, 2023
1 parent 2611263 commit 12a386b
Show file tree
Hide file tree
Showing 12 changed files with 212 additions and 23 deletions.
3 changes: 2 additions & 1 deletion plugins/base/api/base.api
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ public final class org/jetbrains/dokka/base/renderers/html/NavigationNodeIcon :
public static final field INTERFACE Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field INTERFACE_KT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field OBJECT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field TYPEALIAS_KT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field VAL Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field VAR Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static fun valueOf (Ljava/lang/String;)Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
Expand Down Expand Up @@ -1464,7 +1465,7 @@ public class org/jetbrains/dokka/base/translators/documentables/DefaultPageCreat
public final fun getLogger ()Lorg/jetbrains/dokka/utilities/DokkaLogger;
protected final fun getMergeImplicitExpectActualDeclarations ()Z
protected final fun getSeparateInheritedMembers ()Z
public fun pageForClasslike (Lorg/jetbrains/dokka/model/DClasslike;)Lorg/jetbrains/dokka/pages/ClasslikePageNode;
public fun pageForClasslike (Lorg/jetbrains/dokka/model/Documentable;)Lorg/jetbrains/dokka/pages/ClasslikePageNode;
public fun pageForClasslikes (Ljava/util/List;)Lorg/jetbrains/dokka/pages/ClasslikePageNode;
public fun pageForEnumEntries (Ljava/util/List;)Lorg/jetbrains/dokka/pages/ClasslikePageNode;
public fun pageForEnumEntry (Lorg/jetbrains/dokka/model/DEnumEntry;)Lorg/jetbrains/dokka/pages/ClasslikePageNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ abstract class NavigationDataProvider {
val isJava = documentable?.hasAnyJavaSources() ?: false

when (documentable) {
is DTypeAlias -> NavigationNodeIcon.TYPEALIAS_KT
is DClass -> when {
documentable.isException -> NavigationNodeIcon.EXCEPTION
documentable.isAbstract() -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ enum class NavigationNodeIcon(
FUNCTION("function"),
EXCEPTION("exception-class"),
OBJECT("object"),
TYPEALIAS_KT("typealias-kt"),
VAL("val"),
VAR("var");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ object AssetsInstaller : PageTransformer {
"images/nav-icons/interface.svg",
"images/nav-icons/interface-kotlin.svg",
"images/nav-icons/object.svg",
"images/nav-icons/typealias-kotlin.svg",
)

override fun invoke(input: RootPageNode) = input.modified(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,34 @@ open class DefaultPageCreator(
open fun pageForModule(m: DModule): ModulePageNode =
ModulePageNode(m.name.ifEmpty { "<root>" }, contentForModule(m), listOf(m), m.packages.map(::pageForPackage))

/**
* We want to generate separated pages for no-actual typealias.
* Actual typealias are displayed on pages for their expect class (trough [ActualTypealias] extra).
*
* @see ActualTypealias
*/
private fun List<Documentable>.filterOutActualTypeAlias(): List<Documentable> {
fun List<Documentable>.hasExpectClass(dri: DRI) = find { it is DClasslike && it.dri == dri && it.expectPresentInSet != null } != null
return this.filterNot { it is DTypeAlias && this.hasExpectClass(it.dri) }
}

open fun pageForPackage(p: DPackage): PackagePageNode = PackagePageNode(
p.name, contentForPackage(p), setOf(p.dri), listOf(p),
if (mergeImplicitExpectActualDeclarations)
p.classlikes.mergeClashingDocumentable().map(::pageForClasslikes) +
(p.classlikes + p.typealiases).filterOutActualTypeAlias()
.mergeClashingDocumentable().map(::pageForClasslikes) +
p.functions.mergeClashingDocumentable().map(::pageForFunctions) +
p.properties.mergeClashingDocumentable().map(::pageForProperties)
else
p.classlikes.renameClashingDocumentable().map(::pageForClasslike) +
(p.classlikes + p.typealiases).filterOutActualTypeAlias()
.renameClashingDocumentable().map(::pageForClasslike) +
p.functions.renameClashingDocumentable().map(::pageForFunction) +
p.properties.mapNotNull(::pageForProperty)
)

open fun pageForEnumEntry(e: DEnumEntry): ClasslikePageNode = pageForEnumEntries(listOf(e))

open fun pageForClasslike(c: DClasslike): ClasslikePageNode = pageForClasslikes(listOf(c))
open fun pageForClasslike(c: Documentable): ClasslikePageNode = pageForClasslikes(listOf(c))

open fun pageForEnumEntries(documentables: List<DEnumEntry>): ClasslikePageNode {
val dri = documentables.dri.also {
Expand All @@ -83,33 +96,38 @@ open class DefaultPageCreator(
)
}

open fun pageForClasslikes(documentables: List<DClasslike>): ClasslikePageNode {
/**
* @param documentables a list of [DClasslike] and [DTypeAlias] with the same dri in different sourceSets
*/
open fun pageForClasslikes(documentables: List<Documentable>): ClasslikePageNode {
val dri = documentables.dri.also {
if (it.size != 1) {
logger.error("Documentable dri should have the same one ${it.first()} inside the one page!")
}
}

val classlikes = documentables.filterIsInstance<DClasslike>()

val constructors =
if (documentables.shouldDocumentConstructors()) {
documentables.flatMap { (it as? WithConstructors)?.constructors ?: emptyList() }
if (classlikes.shouldDocumentConstructors()) {
classlikes.flatMap { (it as? WithConstructors)?.constructors ?: emptyList() }
} else {
emptyList()
}

val classlikes = documentables.flatMap { it.classlikes }
val functions = documentables.flatMap { it.filteredFunctions }
val props = documentables.flatMap { it.filteredProperties }
val entries = documentables.flatMap { if (it is DEnum) it.entries else emptyList() }
val nestedClasslikes = classlikes.flatMap { it.classlikes }
val functions = classlikes.flatMap { it.filteredFunctions }
val props = classlikes.flatMap { it.filteredProperties }
val entries = classlikes.flatMap { if (it is DEnum) it.entries else emptyList() }

val childrenPages = constructors.map(::pageForFunction) +
if (mergeImplicitExpectActualDeclarations)
classlikes.mergeClashingDocumentable().map(::pageForClasslikes) +
nestedClasslikes.mergeClashingDocumentable().map(::pageForClasslikes) +
functions.mergeClashingDocumentable().map(::pageForFunctions) +
props.mergeClashingDocumentable().map(::pageForProperties) +
entries.mergeClashingDocumentable().map(::pageForEnumEntries)
else
classlikes.renameClashingDocumentable().map(::pageForClasslike) +
nestedClasslikes.renameClashingDocumentable().map(::pageForClasslike) +
functions.renameClashingDocumentable().map(::pageForFunction) +
props.renameClashingDocumentable().mapNotNull(::pageForProperty) +
entries.renameClashingDocumentable().map(::pageForEnumEntry)
Expand Down Expand Up @@ -329,7 +347,7 @@ open class DefaultPageCreator(
sortedWith(compareBy({ it.name }, { it.parameters.size }, { it.dri.toString() }))

/**
* @param documentables a list of [DClasslike] and [DEnumEntry] with the same dri in different sourceSets
* @param documentables a list of [DClasslike] and [DEnumEntry] and [DTypeAlias] with the same dri in different sourceSets
*/
protected open fun contentForClasslikesAndEntries(documentables: List<Documentable>): ContentGroup =
contentBuilder.contentFor(documentables.dri, documentables.sourceSets) {
Expand Down Expand Up @@ -477,8 +495,7 @@ open class DefaultPageCreator(
.takeIf { documentable is DProperty }
}?.let {
group(sourceSets = setOf(sourceSet), kind = ContentKind.BriefComment) {
if (documentable.hasSeparatePage) createBriefComment(documentable, sourceSet, it)
else comment(it.root)
createBriefComment(documentable, sourceSet, it)
}
}
}
Expand Down Expand Up @@ -689,6 +706,7 @@ open class DefaultPageCreator(
protected open fun TagWrapper.toHeaderString() = this.javaClass.toGenericString().split('.').last()
}


internal val List<Documentable>.sourceSets: Set<DokkaSourceSet>
get() = flatMap { it.sourceSets }.toSet()

Expand All @@ -706,9 +724,6 @@ internal val Documentable.descriptions: SourceSetDependent<Description>
internal val Documentable.customTags: Map<String, SourceSetDependent<CustomTagWrapper>>
get() = groupedTags.withTypeNamed()

private val Documentable.hasSeparatePage: Boolean
get() = this !is DTypeAlias

/**
* @see DefaultPageCreator.sortDivergentElementsDeterministically for usage
*/
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions plugins/base/src/main/resources/dokka/styles/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,10 @@ code:not(.block) {
content: url("../images/nav-icons/object.svg");
}

.nav-icon.typealias-kt::before {
content: url("../images/nav-icons/typealias-kotlin.svg");
}

.nav-icon.val::before {
content: url("../images/nav-icons/field-value.svg");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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.DPackage
import org.jetbrains.dokka.model.dfs
import org.jetbrains.dokka.pages.*
import org.junit.jupiter.api.Assertions.assertEquals
Expand Down Expand Up @@ -131,6 +132,32 @@ class ContentForBriefTest : BaseAbstractTest() {
}
}

@Test
fun `brief should work for typealias`() {
testInline(
"""
|/src/main/kotlin/test/source.kt
|package test
|
|/**
|* This is an example <!-- not visible --> of html
|*
|* This is definitely not a brief
|*/
|typealias A = Int
""".trimIndent(),
testConfiguration
) {
pagesTransformationStage = { module ->
val functionBriefDocs = module.singleTypeAliasesDescription("test")

assertEquals(
"This is an example <!-- not visible --> of html",
functionBriefDocs.children.joinToString("") { (it as ContentText).text })
}
}
}

@Test
fun `brief for functions should work with html`() {
testInline(
Expand Down Expand Up @@ -344,4 +371,14 @@ class ContentForBriefTest : BaseAbstractTest() {
val function = functionsTable.children.first()
return function.dfs { it is ContentGroup && it.dci.kind == ContentKind.Comment && it.children.all { it is ContentText } } as ContentGroup
}
private fun RootPageNode.singleTypeAliasesDescription(packageName: String): ContentGroup {
val packagePage =
dfs { it.name == packageName && (it as WithDocumentables).documentables.firstOrNull() is DPackage } as ContentPage
val contentTable =
packagePage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Classlikes } as ContentTable

assertEquals(1, contentTable.children.size)
val row = contentTable.children.first()
return row.dfs { it is ContentGroup && it.dci.kind == ContentKind.Comment && it.children.all { it is ContentText } } as ContentGroup
}
}
78 changes: 78 additions & 0 deletions plugins/base/src/test/kotlin/content/typealiases/TypealiasTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package content.typealiases

import matchers.content.*
import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
import org.jetbrains.dokka.model.dfs
import org.jetbrains.dokka.pages.*
import org.junit.jupiter.api.Test
import utils.assertNotNull


class TypealiasTest : BaseAbstractTest() {
private val configuration = dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/")
classpath = listOf(commonStdlibPath!!)
externalDocumentationLinks = listOf(stdlibExternalDocumentationLink)
}
}
}

@Test
fun `typealias should have a dedicated page with full documentation`() {
testInline(
"""
|/src/main/kotlin/test/Test.kt
|package example
|
| /**
| * Brief text
| *
| * some text
| *
| * @see String
| * @throws Unit
| */
| typealias A = String
""",
configuration
) {
pagesTransformationStage = { module ->
val content = (module.dfs { it.name == "A" } as ClasslikePageNode).content
val platformHinted = content.dfs { it is PlatformHintedContent }
platformHinted.assertNotNull("platformHinted").assertNode {
group {
group {
group {
+"typealias "
group { group { link { +"A" } } }
+" = "
group { link { +"String" } }
}
}

group {
group {
group {
group { +"Brief text" }
group { +"some text" }
}
}
}

header { +"See also" }
table {
group { link { +"String" } }
}

header { +"Throws" }
table {
group { group { link { +"Unit" } } }
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class NavigationIconTest : BaseAbstractTest() {
.filterKeys { it.startsWith("images/nav-icons") }
.keys.sorted()

assertEquals(15, navIconAssets.size)
assertEquals(16, navIconAssets.size)
assertEquals("images/nav-icons/abstract-class-kotlin.svg", navIconAssets[0])
assertEquals("images/nav-icons/abstract-class.svg", navIconAssets[1])
assertEquals("images/nav-icons/annotation-kotlin.svg", navIconAssets[2])
Expand All @@ -53,6 +53,7 @@ class NavigationIconTest : BaseAbstractTest() {
assertEquals("images/nav-icons/interface-kotlin.svg", navIconAssets[12])
assertEquals("images/nav-icons/interface.svg", navIconAssets[13])
assertEquals("images/nav-icons/object.svg", navIconAssets[14])
assertEquals("images/nav-icons/typealias-kotlin.svg", navIconAssets[15])
}
}
}
Expand Down Expand Up @@ -99,6 +100,15 @@ class NavigationIconTest : BaseAbstractTest() {
)
}

@Test
fun `should add icon styles to kotlin typealias navigation item`() {
assertNavigationIcon(
source = kotlinSource("typealias KotlinTypealias = String"),
expectedIconClass = "typealias-kt",
expectedNavLinkText = "KotlinTypealias"
)
}

@Test
fun `should add icon styles to kotlin enum navigation item`() {
assertNavigationIcon(
Expand Down
6 changes: 3 additions & 3 deletions plugins/base/src/test/kotlin/signatures/SignatureTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ class SignatureTest : BaseAbstractTest() {
pluginOverrides = listOf(writerPlugin)
) {
renderingStage = { _, _ ->
writerPlugin.writer.renderedContent("root/example.html").firstSignature().match(
writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
"typealias ", A("PlainTypealias"), " = ", A("Int"),
ignoreSpanWithTokenStyle = true
)
Expand Down Expand Up @@ -663,7 +663,7 @@ class SignatureTest : BaseAbstractTest() {
pluginOverrides = listOf(writerPlugin)
) {
renderingStage = { _, _ ->
writerPlugin.writer.renderedContent("root/example.html").firstSignature().match(
writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
"typealias ", A("PlainTypealias"), " = ", A("Comparable"),
"<", A("Int"), ">",
ignoreSpanWithTokenStyle = true
Expand All @@ -690,7 +690,7 @@ class SignatureTest : BaseAbstractTest() {
pluginOverrides = listOf(writerPlugin)
) {
renderingStage = { _, _ ->
writerPlugin.writer.renderedContent("root/example.html").firstSignature().match(
writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
"typealias ", A("GenericTypealias"), "<", A("T"), "> = ", A("Comparable"),
"<", A("T"), ">",
ignoreSpanWithTokenStyle = true
Expand Down

0 comments on commit 12a386b

Please sign in to comment.