diff options
author | Ignat Beresnev <ignat.beresnev@jetbrains.com> | 2022-08-18 19:33:53 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-18 19:33:53 +0200 |
commit | 50a3323322265ff3b5dab1d861a25bbb1167812a (patch) | |
tree | 0966cfab6d9155724a65439a5c0d1476d66b0a7a /plugins/base/src | |
parent | df8d9879b818799c83ff731b3a78e7d2b96fd8e5 (diff) | |
download | dokka-50a3323322265ff3b5dab1d861a25bbb1167812a.tar.gz dokka-50a3323322265ff3b5dab1d861a25bbb1167812a.tar.bz2 dokka-50a3323322265ff3b5dab1d861a25bbb1167812a.zip |
Add deprecation details block (#2622)
Diffstat (limited to 'plugins/base/src')
17 files changed, 987 insertions, 83 deletions
diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt index 945fff38..f5c3854c 100644 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -95,6 +95,7 @@ open class HtmlRenderer( childrenCallback() } node.hasStyle(ContentStyle.KDocTag) -> span("kdoc-tag") { childrenCallback() } + node.hasStyle(ContentStyle.Footnote) -> div("footnote") { childrenCallback() } node.hasStyle(TextStyle.BreakableAfter) -> { span { childrenCallback() } wbr { } @@ -124,10 +125,12 @@ open class HtmlRenderer( node.dci.kind == ContentKind.Cover -> div("cover $additionalClasses") { //TODO this can be removed childrenCallback() } + node.dci.kind == ContentKind.Deprecation -> div("deprecation-content") { childrenCallback() } node.hasStyle(TextStyle.Paragraph) -> p(additionalClasses) { childrenCallback() } node.hasStyle(TextStyle.Block) -> div(additionalClasses) { childrenCallback() } node.hasStyle(TextStyle.Quotation) -> blockQuote(additionalClasses) { childrenCallback() } node.hasStyle(TextStyle.FloatingRight) -> span("clearfix") { span("floating-right") { childrenCallback() } } + node.hasStyle(TextStyle.Strikethrough) -> strike { childrenCallback() } node.isAnchorable -> buildAnchor( node.anchor!!, node.anchorLabel!!, diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt index 958488ef..ecce70e8 100644 --- a/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt +++ b/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt @@ -1,6 +1,8 @@ package org.jetbrains.dokka.base.renderers.html import org.jetbrains.dokka.base.renderers.sourceSets +import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.annotations +import org.jetbrains.dokka.base.transformers.documentables.isDeprecated import org.jetbrains.dokka.base.transformers.documentables.isException import org.jetbrains.dokka.base.translators.documentables.DocumentableLanguage import org.jetbrains.dokka.base.translators.documentables.documentableLanguage @@ -17,6 +19,7 @@ abstract class NavigationDataProvider { dri = page.dri.first(), sourceSets = page.sourceSets(), icon = chooseNavigationIcon(page), + styles = chooseStyles(page), children = page.navigableChildren() ) @@ -31,8 +34,8 @@ abstract class NavigationDataProvider { name } - private fun chooseNavigationIcon(contentPage: ContentPage): NavigationNodeIcon? { - return if (contentPage is WithDocumentables) { + private fun chooseNavigationIcon(contentPage: ContentPage): NavigationNodeIcon? = + if (contentPage is WithDocumentables) { val documentable = contentPage.documentables.firstOrNull() val isJava = documentable?.hasAnyJavaSources() ?: false @@ -61,27 +64,41 @@ abstract class NavigationDataProvider { } else { null } - } private fun Documentable.hasAnyJavaSources(): Boolean { val withSources = this as? WithSources ?: return false return this.sourceSets.any { withSources.documentableLanguage(it) == DocumentableLanguage.JAVA } } - private fun DClass.isAbstract(): Boolean { - return modifier.values.all { it is KotlinModifier.Abstract || it is JavaModifier.Abstract } + private fun DClass.isAbstract() = + modifier.values.all { it is KotlinModifier.Abstract || it is JavaModifier.Abstract } + + private fun chooseStyles(page: ContentPage): Set<Style> = + if (page.containsOnlyDeprecatedDocumentables()) setOf(TextStyle.Strikethrough) else emptySet() + + private fun ContentPage.containsOnlyDeprecatedDocumentables(): Boolean { + if (this !is WithDocumentables) { + return false + } + return this.documentables.isNotEmpty() && this.documentables.all { it.isDeprecatedForAllSourceSets() } + } + + private fun Documentable.isDeprecatedForAllSourceSets(): Boolean { + val sourceSetAnnotations = this.annotations() + return sourceSetAnnotations.isNotEmpty() && sourceSetAnnotations.all { (_, annotations) -> + annotations.any { it.isDeprecated() } + } } - private fun ContentPage.navigableChildren(): List<NavigationNode> { - return if (this is ClasslikePage) { - return this.navigableChildren() + private fun ContentPage.navigableChildren() = + if (this is ClasslikePage) { + this.navigableChildren() } else { children .filterIsInstance<ContentPage>() .map { visit(it) } .sortedBy { it.name.toLowerCase() } } - } private fun ClasslikePage.navigableChildren(): List<NavigationNode> { // Classlikes should only have other classlikes as navigable children diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt index 87808add..fc17983d 100644 --- a/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt +++ b/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt @@ -57,17 +57,27 @@ class NavigationPage( span("nav-link-grid") { span("nav-link-child ${node.icon?.style()}") span("nav-link-child") { - buildBreakableText(node.name) + nodeText(node) } } } else { - buildBreakableText(node.name) + nodeText(node) } } } node.children.withIndex().forEach { (n, p) -> visit(p, "$navId-$n", renderer) } } } + + private fun FlowContent.nodeText(node: NavigationNode) { + if (node.styles.contains(TextStyle.Strikethrough)) { + strike { + buildBreakableText(node.name) + } + } else { + buildBreakableText(node.name) + } + } } data class NavigationNode( @@ -75,6 +85,7 @@ data class NavigationNode( val dri: DRI, val sourceSets: Set<DisplaySourceSet>, val icon: NavigationNodeIcon?, + val styles: Set<Style> = emptySet(), override val children: List<NavigationNode> ) : WithChildren<NavigationNode> @@ -108,4 +119,4 @@ fun NavigationPage.transform(block: (NavigationNode) -> NavigationNode) = NavigationPage(root.transform(block), moduleName, context) fun NavigationNode.transform(block: (NavigationNode) -> NavigationNode) = - run(block).let { NavigationNode(it.name, it.dri, it.sourceSets, it.icon, it.children.map(block)) } + run(block).let { NavigationNode(it.name, it.dri, it.sourceSets, it.icon, it.styles, it.children.map(block)) } diff --git a/plugins/base/src/main/kotlin/signatures/JvmSignatureUtils.kt b/plugins/base/src/main/kotlin/signatures/JvmSignatureUtils.kt index dc5a9543..7ed7ff3f 100644 --- a/plugins/base/src/main/kotlin/signatures/JvmSignatureUtils.kt +++ b/plugins/base/src/main/kotlin/signatures/JvmSignatureUtils.kt @@ -20,6 +20,9 @@ interface JvmSignatureUtils { fun Collection<ExtraModifiers>.toSignatureString(): String = joinToString("") { it.name.toLowerCase() + " " } + @Suppress("UNCHECKED_CAST") + fun Documentable.annotations() = (this as? WithExtraProperties<Documentable>)?.annotations() ?: emptyMap() + fun <T : AnnotationTarget> WithExtraProperties<T>.annotations(): SourceSetDependent<List<Annotations.Annotation>> = extra[Annotations]?.directAnnotations ?: emptyMap() diff --git a/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt b/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt index 24ed0765..2692928b 100644 --- a/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt +++ b/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt @@ -72,12 +72,12 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog contentBuilder.contentFor( e, ContentKind.Symbol, - setOf(TextStyle.Monospace) + e.stylesIfDeprecated(it), + setOf(TextStyle.Monospace), sourceSets = setOf(it) ) { group(styles = setOf(TextStyle.Block)) { annotationsBlock(e) - link(e.name, e.dri, styles = emptySet()) + link(e.name, e.dri, styles = mainStyles + e.stylesIfDeprecated(it)) } } } @@ -91,12 +91,12 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog return contentBuilder.contentFor( c, ContentKind.Symbol, - setOf(TextStyle.Monospace) + deprecationStyles, + setOf(TextStyle.Monospace), sourceSets = setOf(sourceSet) ) { keyword("actual ") keyword("typealias ") - link(c.name.orEmpty(), c.dri) + link(c.name.orEmpty(), c.dri, styles = mainStyles + deprecationStyles) operator(" = ") signatureForProjection(aliasedType) } @@ -142,7 +142,7 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog return contentBuilder.contentFor( c, ContentKind.Symbol, - setOf(TextStyle.Monospace) + deprecationStyles, + setOf(TextStyle.Monospace), sourceSets = setOf(sourceSet) ) { annotationsBlock(c) @@ -183,7 +183,7 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog keyword("annotation class ") } } - link(c.name!!, c.dri) + link(c.name!!, c.dri, styles = mainStyles + deprecationStyles) if (c is WithGenerics) { list(c.generics, prefix = "<", suffix = ">", separatorStyles = mainStyles + TokenStyle.Punctuation, @@ -251,7 +251,7 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog contentBuilder.contentFor( p, ContentKind.Symbol, - setOf(TextStyle.Monospace) + p.stylesIfDeprecated(sourceSet), + setOf(TextStyle.Monospace), sourceSets = setOf(sourceSet) ) { annotationsBlock(p) @@ -272,7 +272,7 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog signatureForProjection(it.type) punctuation(".") } - link(p.name, p.dri) + link(p.name, p.dri, styles = mainStyles + p.stylesIfDeprecated(sourceSet)) operator(": ") signatureForProjection(p.type) defaultValueAssign(p, sourceSet) @@ -298,7 +298,7 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog contentBuilder.contentFor( f, ContentKind.Symbol, - setOf(TextStyle.Monospace) + f.stylesIfDeprecated(sourceSet), + setOf(TextStyle.Monospace), sourceSets = setOf(sourceSet) ) { annotationsBlock(f) @@ -320,7 +320,7 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog signatureForProjection(it.type) punctuation(".") } - link(f.name, f.dri, styles = mainStyles + TokenStyle.Function) + link(f.name, f.dri, styles = mainStyles + TokenStyle.Function + f.stylesIfDeprecated(sourceSet)) // for a function, opening and closing parentheses must be present // anyway, even if it has no parameters, resulting in `fun test(): R` @@ -357,14 +357,16 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog +contentBuilder.contentFor( t, ContentKind.Symbol, - setOf(TextStyle.Monospace) + t.stylesIfDeprecated(it), + setOf(TextStyle.Monospace), sourceSets = platforms.toSet() ) { annotationsBlock(t) t.visibility[it]?.takeIf { it !in ignoredVisibilities }?.name?.let { keyword("$it ") } processExtraModifiers(t) keyword("typealias ") - signatureForProjection(t.type) + group(styles = mainStyles + t.stylesIfDeprecated(it)) { + signatureForProjection(t.type) + } operator(" = ") signatureForTypealiasTarget(t, type) } @@ -374,10 +376,15 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog private fun signature(t: DTypeParameter) = t.sourceSets.map { - contentBuilder.contentFor(t, styles = t.stylesIfDeprecated(it), sourceSets = setOf(it)) { - signatureForProjection(t.variantTypeParameter.withDri(t.dri.withTargetToDeclaration())) - list(t.nontrivialBounds, prefix = " : ", - surroundingCharactersStyle = mainStyles + TokenStyle.Operator) { bound -> + contentBuilder.contentFor(t, sourceSets = setOf(it)) { + group(styles = mainStyles + t.stylesIfDeprecated(it)) { + signatureForProjection(t.variantTypeParameter.withDri(t.dri.withTargetToDeclaration())) + } + list( + elements = t.nontrivialBounds, + prefix = " : ", + surroundingCharactersStyle = mainStyles + TokenStyle.Operator + ) { bound -> signatureForProjection(bound) } } diff --git a/plugins/base/src/main/kotlin/signatures/KotlinSignatureUtils.kt b/plugins/base/src/main/kotlin/signatures/KotlinSignatureUtils.kt index 381fadf1..ae5275a5 100644 --- a/plugins/base/src/main/kotlin/signatures/KotlinSignatureUtils.kt +++ b/plugins/base/src/main/kotlin/signatures/KotlinSignatureUtils.kt @@ -1,6 +1,8 @@ package org.jetbrains.dokka.base.signatures import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder +import org.jetbrains.dokka.base.transformers.pages.annotations.SinceKotlinTransformer +import org.jetbrains.dokka.pages.ContentKind import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.links.DriOfAny import org.jetbrains.dokka.links.DriOfUnit @@ -10,12 +12,21 @@ import org.jetbrains.dokka.model.properties.WithExtraProperties object KotlinSignatureUtils : JvmSignatureUtils { + private const val classExtension = "::class" private val strategy = OnlyOnce private val listBrackets = Pair('[', ']') - private val classExtension = "::class" private val ignoredAnnotations = setOf( + /** + * Rendered separately, see [SinceKotlinTransformer] + */ Annotations.Annotation(DRI("kotlin", "SinceKotlin"), emptyMap()), - Annotations.Annotation(DRI("kotlin", "Deprecated"), emptyMap()) + + /** + * Rendered separately as its own block, see usage of [ContentKind.Deprecation] + */ + Annotations.Annotation(DRI("kotlin", "Deprecated"), emptyMap()), + Annotations.Annotation(DRI("kotlin", "DeprecatedSinceKotlin"), emptyMap()), + Annotations.Annotation(DRI("java.lang", "Deprecated"), emptyMap()), // could be used as well for interop ) diff --git a/plugins/base/src/main/kotlin/transformers/documentables/utils.kt b/plugins/base/src/main/kotlin/transformers/documentables/utils.kt index 2a5fbc11..079cebea 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/utils.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/utils.kt @@ -5,16 +5,26 @@ import org.jetbrains.dokka.model.Documentable import org.jetbrains.dokka.model.ExceptionInSupertypes import org.jetbrains.dokka.model.properties.WithExtraProperties -fun <T> T.isDeprecated() where T : WithExtraProperties<out Documentable> = - deprecatedAnnotation != null +val <T : WithExtraProperties<out Documentable>> T.isException: Boolean + get() = extra[ExceptionInSupertypes] != null + val <T> T.deprecatedAnnotation where T : WithExtraProperties<out Documentable> get() = extra[Annotations]?.let { annotations -> annotations.directAnnotations.values.flatten().firstOrNull { - it.dri.toString() == "kotlin/Deprecated///PointingToDeclaration/" || - it.dri.toString() == "java.lang/Deprecated///PointingToDeclaration/" + it.isDeprecated() } } -val <T : WithExtraProperties<out Documentable>> T.isException: Boolean - get() = extra[ExceptionInSupertypes] != null
\ No newline at end of file +/** + * @return true if [T] has [kotlin.Deprecated] or [java.lang.Deprecated] + * annotation for **any** source set + */ +fun <T> T.isDeprecated() where T : WithExtraProperties<out Documentable> = deprecatedAnnotation != null + +/** + * @return true for [kotlin.Deprecated] and [java.lang.Deprecated] + */ +fun Annotations.Annotation.isDeprecated() = + (this.dri.packageName == "kotlin" && this.dri.classNames == "Deprecated") || + (this.dri.packageName == "java.lang" && this.dri.classNames == "Deprecated") diff --git a/plugins/base/src/main/kotlin/transformers/pages/merger/SameMethodNamePageMergerStrategy.kt b/plugins/base/src/main/kotlin/transformers/pages/merger/SameMethodNamePageMergerStrategy.kt index 003d68cf..87ff14a4 100644 --- a/plugins/base/src/main/kotlin/transformers/pages/merger/SameMethodNamePageMergerStrategy.kt +++ b/plugins/base/src/main/kotlin/transformers/pages/merger/SameMethodNamePageMergerStrategy.kt @@ -1,8 +1,11 @@ package org.jetbrains.dokka.base.transformers.pages.merger import org.jetbrains.dokka.base.renderers.sourceSets +import org.jetbrains.dokka.base.transformers.documentables.isDeprecated import org.jetbrains.dokka.model.DisplaySourceSet +import org.jetbrains.dokka.model.Documentable import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.utilities.DokkaLogger @@ -12,7 +15,12 @@ import org.jetbrains.dokka.utilities.DokkaLogger */ class SameMethodNamePageMergerStrategy(val logger: DokkaLogger) : PageMergerStrategy { override fun tryMerge(pages: List<PageNode>, path: List<String>): List<PageNode> { - val members = pages.filterIsInstance<MemberPageNode>().takeIf { it.isNotEmpty() } ?: return pages + val members = pages + .filterIsInstance<MemberPageNode>() + .takeIf { it.isNotEmpty() } + ?.sortedBy { it.containsDeprecatedDocumentables() } // non-deprecated first + ?: return pages + val name = pages.first().name.also { if (pages.any { page -> page.name != it }) { // Is this even possible? logger.error("Page names for $it do not match!") @@ -33,6 +41,10 @@ class SameMethodNamePageMergerStrategy(val logger: DokkaLogger) : PageMergerStra return (pages - members) + listOf(merged) } + @Suppress("UNCHECKED_CAST") + private fun MemberPageNode.containsDeprecatedDocumentables() = + this.documentables.any { (it as? WithExtraProperties<Documentable>)?.isDeprecated() == true } + private fun List<MemberPageNode>.allSourceSets(): Set<DisplaySourceSet> = fold(emptySet()) { acc, e -> acc + e.sourceSets() } diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index f08b2056..de31e448 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -452,6 +452,8 @@ open class DefaultPageCreator( 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 -> diff --git a/plugins/base/src/main/kotlin/translators/documentables/DeprecationSectionCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DeprecationSectionCreator.kt new file mode 100644 index 00000000..73c36d8d --- /dev/null +++ b/plugins/base/src/main/kotlin/translators/documentables/DeprecationSectionCreator.kt @@ -0,0 +1,190 @@ +package org.jetbrains.dokka.base.translators.documentables + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.annotations +import org.jetbrains.dokka.base.transformers.documentables.isDeprecated +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.pages.ContentKind +import org.jetbrains.dokka.pages.ContentStyle +import org.jetbrains.dokka.pages.TextStyle +import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder.DocumentableContentBuilder + +/** + * Main header for [Deprecated] section + */ +private const val DEPRECATED_HEADER_LEVEL = 3 + +/** + * Header for a direct parameter of [Deprecated] annotation, + * such as [Deprecated.message] and [Deprecated.replaceWith] + */ +private const val DIRECT_PARAM_HEADER_LEVEL = 4 + +internal fun PageContentBuilder.DocumentableContentBuilder.deprecatedSectionContent( + documentable: Documentable, + platforms: Set<DokkaConfiguration.DokkaSourceSet> +) { + val allAnnotations = documentable.annotations() + if (allAnnotations.isEmpty()) { + return + } + + platforms.forEach { platform -> + val platformAnnotations = allAnnotations[platform] ?: emptyList() + val deprecatedPlatformAnnotations = platformAnnotations.filter { it.isDeprecated() } + + if (deprecatedPlatformAnnotations.isNotEmpty()) { + group(kind = ContentKind.Deprecation, sourceSets = setOf(platform), styles = emptySet()) { + val kotlinAnnotation = deprecatedPlatformAnnotations.find { it.dri.packageName == "kotlin" } + val javaAnnotation = deprecatedPlatformAnnotations.find { it.dri.packageName == "java.lang" } + + // If both annotations are present, priority is given to Kotlin's annotation since it + // contains more useful information, and Java's annotation is probably there + // for interop with Java callers, so it should be OK to ignore it + if (kotlinAnnotation != null) { + createKotlinDeprecatedSectionContent(kotlinAnnotation, platformAnnotations) + } else if (javaAnnotation != null) { + createJavaDeprecatedSectionContent(javaAnnotation) + } + } + } + } +} + +/** + * @see [DeprecatedSinceKotlin] + */ +private fun findDeprecatedSinceKotlinAnnotation(annotations: List<Annotations.Annotation>): Annotations.Annotation? { + return annotations.firstOrNull { + it.dri.packageName == "kotlin" && it.dri.classNames == "DeprecatedSinceKotlin" + } +} + +/** + * Section with details for Kotlin's [kotlin.Deprecated] annotation + */ +private fun DocumentableContentBuilder.createKotlinDeprecatedSectionContent( + deprecatedAnnotation: Annotations.Annotation, + allAnnotations: List<Annotations.Annotation> +) { + val deprecatedSinceKotlinAnnotation = findDeprecatedSinceKotlinAnnotation(allAnnotations) + header( + level = DEPRECATED_HEADER_LEVEL, + text = createKotlinDeprecatedHeaderText(deprecatedAnnotation, deprecatedSinceKotlinAnnotation) + ) + + deprecatedSinceKotlinAnnotation?.let { + createDeprecatedSinceKotlinFootnoteContent(it) + } + + deprecatedAnnotation.takeStringParam("message")?.let { + group(styles = setOf(TextStyle.Paragraph)) { + text(it) + } + } + + createReplaceWithSectionContent(deprecatedAnnotation) +} + +private fun createKotlinDeprecatedHeaderText( + kotlinDeprecatedAnnotation: Annotations.Annotation, + deprecatedSinceKotlinAnnotation: Annotations.Annotation? +): String { + if (deprecatedSinceKotlinAnnotation != null) { + // In this case there's no single level, it's dynamic based on api version, + // so there should be a footnote with levels and their respective versions + return "Deprecated" + } + + val deprecationLevel = kotlinDeprecatedAnnotation.params["level"]?.let { (it as? EnumValue)?.enumName } + return when (deprecationLevel) { + "DeprecationLevel.ERROR" -> "Deprecated (with error)" + "DeprecationLevel.HIDDEN" -> "Deprecated (hidden)" + else -> "Deprecated" + } +} + +/** + * Footnote for [DeprecatedSinceKotlin] annotation used in stdlib + * + * Notice that values are empty by default, so it's not guaranteed that all three will be set + */ +private fun DocumentableContentBuilder.createDeprecatedSinceKotlinFootnoteContent( + deprecatedSinceKotlinAnnotation: Annotations.Annotation +) { + group(styles = setOf(ContentStyle.Footnote)) { + deprecatedSinceKotlinAnnotation.takeStringParam("warningSince")?.let { + group(styles = setOf(TextStyle.Paragraph)) { + text("Warning since $it") + } + } + deprecatedSinceKotlinAnnotation.takeStringParam("errorSince")?.let { + group(styles = setOf(TextStyle.Paragraph)) { + text("Error since $it") + } + } + deprecatedSinceKotlinAnnotation.takeStringParam("hiddenSince")?.let { + group(styles = setOf(TextStyle.Paragraph)) { + text("Hidden since $it") + } + } + } +} + +/** + * Section for [ReplaceWith] parameter of [kotlin.Deprecated] annotation + */ +private fun DocumentableContentBuilder.createReplaceWithSectionContent(kotlinDeprecatedAnnotation: Annotations.Annotation) { + val replaceWithAnnotation = (kotlinDeprecatedAnnotation.params["replaceWith"] as? AnnotationValue)?.annotation + ?: return + + header( + level = DIRECT_PARAM_HEADER_LEVEL, + text = "Replace with" + ) + + // Signature: vararg val imports: String + val imports = (replaceWithAnnotation.params["imports"] as? ArrayValue) + ?.value + ?.mapNotNull { (it as? StringValue)?.value } + ?: emptyList() + + if (imports.isNotEmpty()) { + codeBlock(language = "kotlin", styles = setOf(TextStyle.Monospace)) { + imports.forEach { + text("import $it") + breakLine() + } + } + } + + replaceWithAnnotation.takeStringParam("expression")?.removeSurrounding("`")?.let { + codeBlock(language = "kotlin", styles = setOf(TextStyle.Monospace)) { + text(it) + } + } +} + +/** + * Section with details for Java's [java.lang.Deprecated] annotation + */ +private fun DocumentableContentBuilder.createJavaDeprecatedSectionContent( + deprecatedAnnotation: Annotations.Annotation, +) { + val isForRemoval = deprecatedAnnotation.takeBooleanParam("forRemoval", default = false) + header( + level = DEPRECATED_HEADER_LEVEL, + text = if (isForRemoval) "Deprecated (for removal)" else "Deprecated" + ) + deprecatedAnnotation.takeStringParam("since")?.let { + group(styles = setOf(ContentStyle.Footnote)) { + text("Since version $it") + } + } +} + +private fun Annotations.Annotation.takeBooleanParam(name: String, default: Boolean): Boolean = + (this.params[name] as? BooleanValue)?.value ?: default + +private fun Annotations.Annotation.takeStringParam(name: String): String? = + (this.params[name] as? StringValue)?.takeIf { it.value.isNotEmpty() }?.value diff --git a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt index 02b2e0d9..000c955f 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt @@ -139,6 +139,10 @@ open class PageContentBuilder( contents += createText(text, kind, sourceSets, styles, extra) } + fun breakLine(sourceSets: Set<DokkaSourceSet> = mainSourcesetData) { + contents += ContentBreakLine(sourceSets.toDisplaySourceSets()) + } + fun buildSignature(d: Documentable) = signatureProvider.signature(d) fun table( diff --git a/plugins/base/src/main/resources/dokka/styles/style.css b/plugins/base/src/main/resources/dokka/styles/style.css index 8e379c75..0d1085c1 100644 --- a/plugins/base/src/main/resources/dokka/styles/style.css +++ b/plugins/base/src/main/resources/dokka/styles/style.css @@ -743,6 +743,39 @@ small { font-size: 11px; } +.deprecation-content { + margin: 20px 10px; + border:1px solid var(--border-color); + padding: 13px 15px 16px 15px; +} + +.deprecation-content > h3 { + margin-top: 0; + margin-bottom: 0; +} + +.deprecation-content > h4 { + font-size: 16px; + margin-top: 15px; + margin-bottom: 0; +} + +.deprecation-content code.block { + padding: 5px 10px; + display: inline-block; +} + +.deprecation-content .footnote { + margin-left: 25px; + font-size: 13px; + font-weight: bold; + display: block; +} + +.deprecation-content .footnote > p { + margin: 0; +} + .platform-tag { display: flex; flex-direction: row; diff --git a/plugins/base/src/test/kotlin/content/annotations/JavaDeprecatedTest.kt b/plugins/base/src/test/kotlin/content/annotations/JavaDeprecatedTest.kt new file mode 100644 index 00000000..961ce5f5 --- /dev/null +++ b/plugins/base/src/test/kotlin/content/annotations/JavaDeprecatedTest.kt @@ -0,0 +1,139 @@ +package content.annotations + +import matchers.content.* +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.base.transformers.documentables.deprecatedAnnotation +import org.jetbrains.dokka.base.transformers.documentables.isDeprecated +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.properties.WithExtraProperties +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.ContentStyle +import org.junit.jupiter.api.Test +import utils.pWrapped +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class JavaDeprecatedTest : BaseAbstractTest() { + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `should assert util functions for deprecation`() { + testInline( + """ + |/src/main/kotlin/deprecated/DeprecatedJavaClass.java + |package deprecated + | + |@Deprecated(forRemoval = true) + |public class DeprecatedJavaClass {} + """.trimIndent(), + testConfiguration + ) { + documentablesTransformationStage = { module -> + val deprecatedClass = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "DeprecatedJavaClass" } + + val isDeprecated = (deprecatedClass as WithExtraProperties<out Documentable>).isDeprecated() + assertTrue(isDeprecated) + + val deprecatedAnnotation = (deprecatedClass as WithExtraProperties<out Documentable>).deprecatedAnnotation + checkNotNull(deprecatedAnnotation) + + assertTrue(deprecatedAnnotation.isDeprecated()) + assertEquals("java.lang", deprecatedAnnotation.dri.packageName) + assertEquals("Deprecated", deprecatedAnnotation.dri.classNames) + } + } + } + + @Test + fun `should change deprecated header if marked for removal`() { + testInline( + """ + |/src/main/kotlin/deprecated/DeprecatedJavaClass.java + |package deprecated + | + |/** + | * Average function description + | */ + |@Deprecated(forRemoval = true) + |public class DeprecatedJavaClass {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val deprecatedJavaClass = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "DeprecatedJavaClass" } as ContentPage + + deprecatedJavaClass.content.assertNode { + group { + header(1) { +"DeprecatedJavaClass" } + platformHinted { + skipAllNotMatching() + group { + header(3) { + +"Deprecated (for removal)" + } + } + group { pWrapped("Average function description") } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `should add footnote for 'since' param`() { + testInline( + """ + |/src/main/kotlin/deprecated/DeprecatedJavaClass.java + |package deprecated + | + |/** + | * Average function description + | */ + |@Deprecated(since = "11") + |public class DeprecatedJavaClass {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val deprecatedJavaClass = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "DeprecatedJavaClass" } as ContentPage + + deprecatedJavaClass.content.assertNode { + group { + header(1) { +"DeprecatedJavaClass" } + platformHinted { + skipAllNotMatching() + group { + header(3) { + +"Deprecated" + } + group { + check { assertEquals(ContentStyle.Footnote, this.style.firstOrNull()) } + +"Since version 11" + } + } + group { pWrapped("Average function description") } + } + } + skipAllNotMatching() + } + } + } + } +} diff --git a/plugins/base/src/test/kotlin/content/annotations/KotlinDeprecatedTest.kt b/plugins/base/src/test/kotlin/content/annotations/KotlinDeprecatedTest.kt new file mode 100644 index 00000000..8b311893 --- /dev/null +++ b/plugins/base/src/test/kotlin/content/annotations/KotlinDeprecatedTest.kt @@ -0,0 +1,395 @@ +package content.annotations + +import matchers.content.* +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.base.transformers.documentables.deprecatedAnnotation +import org.jetbrains.dokka.pages.ContentStyle +import org.jetbrains.dokka.base.transformers.documentables.isDeprecated +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.properties.WithExtraProperties +import org.junit.jupiter.api.Test +import utils.ParamAttributes +import utils.bareSignature +import utils.pWrapped +import kotlin.test.assertEquals +import kotlin.test.assertTrue + + +class KotlinDeprecatedTest : BaseAbstractTest() { + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `should assert util functions for deprecation`() { + testInline( + """ + |/src/main/kotlin/kotlin/KotlinFile.kt + |package kotlin + | + |@Deprecated( + | message = "Fancy message" + |) + |fun simpleFunction() {} + """.trimIndent(), + testConfiguration + ) { + documentablesTransformationStage = { module -> + val deprecatedFunction = module.children + .single { it.name == "kotlin" }.children + .single { it.name == "simpleFunction" } + + val isDeprecated = (deprecatedFunction as WithExtraProperties<out Documentable>).isDeprecated() + assertTrue(isDeprecated) + + val deprecatedAnnotation = (deprecatedFunction as WithExtraProperties<out Documentable>).deprecatedAnnotation + checkNotNull(deprecatedAnnotation) + + assertTrue(deprecatedAnnotation.isDeprecated()) + assertEquals("kotlin", deprecatedAnnotation.dri.packageName) + assertEquals("Deprecated", deprecatedAnnotation.dri.classNames) + } + } + } + + @Test + fun `should change header if deprecation level is not default`() { + testInline( + """ + |/src/main/kotlin/kotlin/DeprecatedKotlin.kt + |package kotlin + | + |/** + | * Average function description + | */ + |@Deprecated( + | message = "Reason for deprecation bla bla", + | level = DeprecationLevel.ERROR + |) + |fun oldLegacyFunction(typedParam: SomeOldType, someLiteral: String): String {} + | + |fun newShinyFunction(typedParam: SomeOldType, someLiteral: String, newTypedParam: SomeNewType): String {} + |class SomeOldType {} + |class SomeNewType {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionWithDeprecatedFunction = module.children + .single { it.name == "kotlin" }.children + .single { it.name == "oldLegacyFunction" } as ContentPage + + functionWithDeprecatedFunction.content.assertNode { + group { + header(1) { +"oldLegacyFunction" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "oldLegacyFunction", + returnType = "String", + params = arrayOf( + "typedParam" to ParamAttributes(emptyMap(), emptySet(), "SomeOldType"), + "someLiteral" to ParamAttributes(emptyMap(), emptySet(), "String"), + ) + ) + } + after { + group { + header(3) { + +"Deprecated (with error)" + } + p { + +"Reason for deprecation bla bla" + } + } + group { pWrapped("Average function description") } + } + } + } + } + } + } + } + + @Test + fun `should display repalceWith param with imports as code blocks`() { + testInline( + """ + |/src/main/kotlin/kotlin/DeprecatedKotlin.kt + |package kotlin + | + |/** + | * Average function description + | */ + |@Deprecated( + | message = "Reason for deprecation bla bla", + | replaceWith = ReplaceWith( + | "newShinyFunction(typedParam, someLiteral, SomeNewType())", + | imports = [ + | "com.example.dokka.debug.newShinyFunction", + | "com.example.dokka.debug.SomeOldType", + | "com.example.dokka.debug.SomeNewType", + | ] + | ), + |) + |fun oldLegacyFunction(typedParam: SomeOldType, someLiteral: String): String {} + | + |fun newShinyFunction(typedParam: SomeOldType, someLiteral: String, newTypedParam: SomeNewType): String {} + |class SomeOldType {} + |class SomeNewType {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionWithDeprecatedFunction = module.children + .single { it.name == "kotlin" }.children + .single { it.name == "oldLegacyFunction" } as ContentPage + + functionWithDeprecatedFunction.content.assertNode { + group { + header(1) { +"oldLegacyFunction" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "oldLegacyFunction", + returnType = "String", + params = arrayOf( + "typedParam" to ParamAttributes(emptyMap(), emptySet(), "SomeOldType"), + "someLiteral" to ParamAttributes(emptyMap(), emptySet(), "String"), + ) + ) + } + after { + group { + header(3) { + +"Deprecated" + } + p { + +"Reason for deprecation bla bla" + } + + header(4) { + +"Replace with" + } + codeBlock { + +"import com.example.dokka.debug.newShinyFunction" + br() + +"import com.example.dokka.debug.SomeOldType" + br() + +"import com.example.dokka.debug.SomeNewType" + br() + } + codeBlock { + +"newShinyFunction(typedParam, someLiteral, SomeNewType())" + } + } + group { pWrapped("Average function description") } + } + } + } + } + } + } + } + + @Test + fun `should add footnote for DeprecatedSinceKotlin annotation`() { + testInline( + """ + |/src/main/kotlin/kotlin/DeprecatedKotlin.kt + |package kotlin + | + |/** + | * Average function description + | */ + |@DeprecatedSinceKotlin( + | warningSince = "1.4", + | errorSince = "1.5", + | hiddenSince = "1.6" + |) + |@Deprecated( + | message = "Deprecation reason bla bla" + |) + |fun oldLegacyFunction(typedParam: SomeOldType, someLiteral: String): String {} + | + |fun newShinyFunction(typedParam: SomeOldType, someLiteral: String, newTypedParam: SomeNewType): String {} + |class SomeOldType {} + |class SomeNewType {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionWithDeprecatedFunction = module.children + .single { it.name == "kotlin" }.children + .single { it.name == "oldLegacyFunction" } as ContentPage + + functionWithDeprecatedFunction.content.assertNode { + group { + header(1) { +"oldLegacyFunction" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "oldLegacyFunction", + returnType = "String", + params = arrayOf( + "typedParam" to ParamAttributes(emptyMap(), emptySet(), "SomeOldType"), + "someLiteral" to ParamAttributes(emptyMap(), emptySet(), "String"), + ) + ) + } + after { + group { + header(3) { + +"Deprecated" + } + group { + check { assertEquals(ContentStyle.Footnote, this.style.firstOrNull()) } + p { + +"Warning since 1.4" + } + p { + +"Error since 1.5" + } + p { + +"Hidden since 1.6" + } + } + p { + +"Deprecation reason bla bla" + } + } + group { pWrapped("Average function description") } + } + } + } + } + } + } + } + + @Test + fun `should generate deprecation block with all parameters present and long description`() { + testInline( + """ + |/src/main/kotlin/kotlin/DeprecatedKotlin.kt + |package kotlin + | + |/** + | * Average function description + | */ + |@Deprecated( + | message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vel vulputate risus. " + + | "Etiam dictum odio vel vulputate auctor.Nulla facilisi. Duis ullamcorper ullamcorper lectus " + + | "nec rutrum. Quisque eu risus eu purus bibendum ultricies. Maecenas tincidunt dui in sodales " + + | "faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin id sem felis. " + + | "Praesent et libero lacinia, egestas libero in, ultrices lectus. Suspendisse eget volutpat " + + | "velit. Phasellus laoreet mi eu egestas mattis.", + | replaceWith = ReplaceWith( + | "newShinyFunction(typedParam, someLiteral, SomeNewType())", + | imports = [ + | "com.example.dokka.debug.newShinyFunction", + | "com.example.dokka.debug.SomeOldType", + | "com.example.dokka.debug.SomeNewType", + | ] + | ), + | level = DeprecationLevel.ERROR + |) + |fun oldLegacyFunction(typedParam: SomeOldType, someLiteral: String): String {} + | + |fun newShinyFunction(typedParam: SomeOldType, someLiteral: String, newTypedParam: SomeNewType): String {} + |class SomeOldType {} + |class SomeNewType {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionWithDeprecatedFunction = module.children + .single { it.name == "kotlin" }.children + .single { it.name == "oldLegacyFunction" } as ContentPage + + functionWithDeprecatedFunction.content.assertNode { + group { + header(1) { +"oldLegacyFunction" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "oldLegacyFunction", + returnType = "String", + params = arrayOf( + "typedParam" to ParamAttributes(emptyMap(), emptySet(), "SomeOldType"), + "someLiteral" to ParamAttributes(emptyMap(), emptySet(), "String"), + ) + ) + } + after { + group { + header(3) { + +"Deprecated (with error)" + } + p { + +("Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + + "Maecenas vel vulputate risus. Etiam dictum odio vel " + + "vulputate auctor.Nulla facilisi. Duis ullamcorper " + + "ullamcorper lectus nec rutrum. Quisque eu risus eu " + + "purus bibendum ultricies. Maecenas tincidunt dui in sodales faucibus. " + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + + "Proin id sem felis. Praesent et libero lacinia, egestas " + + "libero in, ultrices lectus. Suspendisse eget volutpat velit. " + + "Phasellus laoreet mi eu egestas mattis.") + } + header(4) { + +"Replace with" + } + codeBlock { + +"import com.example.dokka.debug.newShinyFunction" + br() + +"import com.example.dokka.debug.SomeOldType" + br() + +"import com.example.dokka.debug.SomeNewType" + br() + } + codeBlock { + +"newShinyFunction(typedParam, someLiteral, SomeNewType())" + } + } + group { pWrapped("Average function description") } + } + } + } + } + } + } + } +} diff --git a/plugins/base/src/test/kotlin/content/annotations/DepredatedAndSinceKotlinTest.kt b/plugins/base/src/test/kotlin/content/annotations/SinceKotlinTest.kt index e0aa1d04..84f5b647 100644 --- a/plugins/base/src/test/kotlin/content/annotations/DepredatedAndSinceKotlinTest.kt +++ b/plugins/base/src/test/kotlin/content/annotations/SinceKotlinTest.kt @@ -1,15 +1,13 @@ package content.annotations - import matchers.content.* -import org.jetbrains.dokka.pages.ContentPage import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.pages.ContentPage import org.junit.jupiter.api.Test import utils.ParamAttributes import utils.bareSignature - -class DepredatedAndSinceKotlinTest : BaseAbstractTest() { +class SinceKotlinTest : BaseAbstractTest() { private val testConfiguration = dokkaConfiguration { sourceSets { @@ -21,46 +19,6 @@ class DepredatedAndSinceKotlinTest : BaseAbstractTest() { } @Test - fun `function with deprecated annotation`() { - testInline( - """ - |/src/main/kotlin/test/source.kt - |package test - | - |@Deprecated("And some things that should not have been forgotten were lost. History became legend. Legend became myth.") - |fun ring(abc: String): String { - | return "My precious " + abc - |} - """.trimIndent(), testConfiguration - ) { - pagesTransformationStage = { module -> - val page = module.children.single { it.name == "test" } - .children.single { it.name == "ring" } as ContentPage - page.content.assertNode { - group { - header(1) { +"ring" } - } - divergentGroup { - divergentInstance { - divergent { - bareSignature( - emptyMap(), - "", - "", - emptySet(), - "ring", - "String", - "abc" to ParamAttributes(emptyMap(), emptySet(), "String") - ) - } - } - } - } - } - } - } - - @Test fun `function with since kotlin annotation`() { testInline( """ diff --git a/plugins/base/src/test/kotlin/renderers/html/NavigationTest.kt b/plugins/base/src/test/kotlin/renderers/html/NavigationTest.kt index 104246cb..13a9e711 100644 --- a/plugins/base/src/test/kotlin/renderers/html/NavigationTest.kt +++ b/plugins/base/src/test/kotlin/renderers/html/NavigationTest.kt @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test import utils.TestOutputWriterPlugin import kotlin.test.assertEquals import utils.navigationHtml +import kotlin.test.assertNull class NavigationTest : BaseAbstractTest() { @@ -19,6 +20,106 @@ class NavigationTest : BaseAbstractTest() { } @Test + fun `should strike deprecated class link`() { + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/main/kotlin/com/example/SimpleDeprecatedClass.kt + |package com.example + | + |@Deprecated("reason") + |class SimpleDeprecatedClass {} + """.trimIndent(), + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.writer.navigationHtml().select("div.sideMenuPart") + assertEquals(3, content.size) + + // Navigation menu should be the following: + // - root + // - com.example + // - SimpleDeprecatedClass + + content[0].assertNavigationLink( + id = "root-nav-submenu", + text = "root", + address = "index.html", + ) + + content[1].assertNavigationLink( + id = "root-nav-submenu-0", + text = "com.example", + address = "root/com.example/index.html", + ) + + content[2].assertNavigationLink( + id = "root-nav-submenu-0-0", + text = "SimpleDeprecatedClass", + address = "root/com.example/-simple-deprecated-class/index.html", + icon = NavigationNodeIcon.CLASS_KT, + isStrikethrough = true + ) + } + } + } + + @Test + fun `should not strike pages where only one of N documentables is deprecated`() { + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/main/kotlin/com/example/File.kt + |package com.example + | + |/** + | * First + | */ + |@Deprecated("reason") + |fun functionWithCommonName() + | + |/** + | * Second + | */ + |fun functionWithCommonName() + """.trimIndent(), + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.writer.navigationHtml().select("div.sideMenuPart") + assertEquals(3, content.size) + + // Navigation menu should be the following: + // - root + // - com.example + // - functionWithCommonName + + content[0].assertNavigationLink( + id = "root-nav-submenu", + text = "root", + address = "index.html", + ) + + content[1].assertNavigationLink( + id = "root-nav-submenu-0", + text = "com.example", + address = "root/com.example/index.html", + ) + + content[2].assertNavigationLink( + id = "root-nav-submenu-0-0", + text = "functionWithCommonName()", + address = "root/com.example/function-with-common-name.html", + icon = NavigationNodeIcon.FUNCTION, + isStrikethrough = false + ) + } + } + } + + @Test fun `should have expandable classlikes`() { val writerPlugin = TestOutputWriterPlugin() testInline( @@ -209,7 +310,7 @@ class NavigationTest : BaseAbstractTest() { } private fun Element.assertNavigationLink( - id: String, text: String, address: String, icon: NavigationNodeIcon? = null + id: String, text: String, address: String, icon: NavigationNodeIcon? = null, isStrikethrough: Boolean = false ) { assertEquals(id, this.id()) @@ -224,5 +325,11 @@ class NavigationTest : BaseAbstractTest() { assertEquals("nav-link-child", iconStyles[0]) assertEquals(icon.style(), "${iconStyles[1]} ${iconStyles[2]}") } + if (isStrikethrough) { + val textInsideStrikethrough = link.selectFirst("strike")?.text() + assertEquals(text, textInsideStrikethrough) + } else { + assertNull(link.selectFirst("strike")) + } } } diff --git a/plugins/base/src/test/kotlin/utils/contentUtils.kt b/plugins/base/src/test/kotlin/utils/contentUtils.kt index 0af253df..08ddf034 100644 --- a/plugins/base/src/test/kotlin/utils/contentUtils.kt +++ b/plugins/base/src/test/kotlin/utils/contentUtils.kt @@ -208,7 +208,9 @@ fun ContentMatcherBuilder<*>.typealiasSignature(name: String, expressionTarget: group { +"typealias " group { - link { +name } + group { + link { +name } + } skipAllNotMatching() } +" = " |