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/main/kotlin/translators | |
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/main/kotlin/translators')
3 files changed, 196 insertions, 0 deletions
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( |