Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip @Deprecated documentables with HIDDEN level #2486

Merged
merged 1 commit into from May 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 2 additions & 3 deletions plugins/base/api/base.api
Expand Up @@ -1124,10 +1124,9 @@ public final class org/jetbrains/dokka/base/transformers/documentables/ClashingD
public fun mergeStrategyFor (Lorg/jetbrains/dokka/base/transformers/documentables/ClashingDriIdentifier;Lorg/jetbrains/dokka/base/transformers/documentables/ClashingDriIdentifier;)Lorg/jetbrains/dokka/model/properties/MergeStrategy;
}

public final class org/jetbrains/dokka/base/transformers/documentables/DeprecatedDocumentableFilterTransformer : org/jetbrains/dokka/transformers/documentation/PreMergeDocumentableTransformer {
public final class org/jetbrains/dokka/base/transformers/documentables/DeprecatedDocumentableFilterTransformer : org/jetbrains/dokka/base/transformers/documentables/SuppressedByConditionDocumentableFilterTransformer {
public fun <init> (Lorg/jetbrains/dokka/plugability/DokkaContext;)V
public final fun getContext ()Lorg/jetbrains/dokka/plugability/DokkaContext;
public fun invoke (Ljava/util/List;)Ljava/util/List;
public fun shouldBeSuppressed (Lorg/jetbrains/dokka/model/Documentable;)Z
}

public abstract class org/jetbrains/dokka/base/transformers/documentables/DocumentableReplacerTransformer : org/jetbrains/dokka/transformers/documentation/PreMergeDocumentableTransformer {
Expand Down
@@ -1,194 +1,57 @@
package org.jetbrains.dokka.base.transformers.documentables

import org.jetbrains.dokka.DokkaConfiguration.PackageOptions
import org.jetbrains.dokka.DokkaConfiguration
import org.jetbrains.dokka.model.*
import org.jetbrains.dokka.model.Annotations
import org.jetbrains.dokka.model.Documentable
import org.jetbrains.dokka.model.EnumValue
import org.jetbrains.dokka.model.properties.WithExtraProperties
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer

class DeprecatedDocumentableFilterTransformer(val context: DokkaContext) : PreMergeDocumentableTransformer {
override fun invoke(modules: List<DModule>) = modules.map { original ->
val sourceSet = original.sourceSets.single()
val packageOptions =
sourceSet.perPackageOptions
original.let {
DeprecatedDocumentableFilter(sourceSet, packageOptions).processModule(it)
}
import org.jetbrains.dokka.transformers.documentation.perPackageOptions
import org.jetbrains.dokka.transformers.documentation.sourceSet

/**
* If [PackageOptions.skipDeprecated] or [DokkaConfiguration.DokkaSourceSet.skipDeprecated] is set
* to `true`, suppresses documentables marked with [kotlin.Deprecated] or [java.lang.Deprecated].
* Package options are given preference over global options.
*
* Documentables with [kotlin.Deprecated.level] set to [DeprecationLevel.HIDDEN]
* are suppressed regardless of global and package options.
*/
class DeprecatedDocumentableFilterTransformer(context: DokkaContext) :
SuppressedByConditionDocumentableFilterTransformer(context) {

override fun shouldBeSuppressed(d: Documentable): Boolean {
val annotations = (d as? WithExtraProperties<*>)?.annotations() ?: return false
if (annotations.isEmpty())
return false

val deprecatedAnnotations = filterDeprecatedAnnotations(annotations)
if (deprecatedAnnotations.isEmpty())
return false

val kotlinDeprecated = deprecatedAnnotations.find { it.dri.packageName == "kotlin" }
if (kotlinDeprecated?.isHidden() == true)
return true

return perPackageOptions(d)?.skipDeprecated ?: sourceSet(d).skipDeprecated
}

private class DeprecatedDocumentableFilter(
val globalOptions: DokkaConfiguration.DokkaSourceSet,
val packageOptions: List<DokkaConfiguration.PackageOptions>
) {

fun <T> T.isAllowedInPackage(): Boolean where T : WithExtraProperties<T>, T : Documentable {
val packageName = this.dri.packageName
val condition = packageName != null && packageOptions.firstOrNull {
Regex(it.matchingRegex).matches(packageName)
}?.skipDeprecated
?: globalOptions.skipDeprecated

return !(condition && this.isDeprecated())
}

fun processModule(original: DModule) =
filterPackages(original.packages).let { (modified, packages) ->
if (!modified) original
else
original.copy(
packages = packages
)
}


private fun filterPackages(packages: List<DPackage>): Pair<Boolean, List<DPackage>> {
var packagesListChanged = false
val filteredPackages = packages.mapNotNull { pckg ->
var modified = false
val functions = filterFunctions(pckg.functions).let { (listModified, list) ->
modified = modified || listModified
list
}
val properties = filterProperties(pckg.properties).let { (listModified, list) ->
modified = modified || listModified
list
}
val classlikes = filterClasslikes(pckg.classlikes).let { (listModified, list) ->
modified = modified || listModified
list
}
val typeAliases = filterTypeAliases(pckg.typealiases).let { (listModified, list) ->
modified = modified || listModified
list
}
when {
!modified -> pckg
else -> {
packagesListChanged = true
pckg.copy(
functions = functions,
properties = properties,
classlikes = classlikes,
typealiases = typeAliases
)
}
}
}
return Pair(packagesListChanged, filteredPackages)
}

private fun filterFunctions(
functions: List<DFunction>
) = functions.filter { it.isAllowedInPackage() }.let {
Pair(it.size != functions.size, it)
private fun WithExtraProperties<*>.annotations(): List<Annotations.Annotation> {
return this.extra.allOfType<Annotations>().flatMap { annotations ->
annotations.directAnnotations.values.singleOrNull() ?: emptyList()
}
}

private fun filterProperties(
properties: List<DProperty>
): Pair<Boolean, List<DProperty>> = properties.filter {
it.isAllowedInPackage()
}.let {
Pair(properties.size != it.size, it)
private fun filterDeprecatedAnnotations(annotations: List<Annotations.Annotation>): List<Annotations.Annotation> {
return annotations.filter {
(it.dri.packageName == "kotlin" && it.dri.classNames == "Deprecated") ||
(it.dri.packageName == "java.lang" && it.dri.classNames == "Deprecated")
}
}

private fun filterEnumEntries(entries: List<DEnumEntry>) =
entries.filter { it.isAllowedInPackage() }.map { entry ->
entry.copy(
functions = filterFunctions(entry.functions).second,
properties = filterProperties(entry.properties).second,
classlikes = filterClasslikes(entry.classlikes).second,
)
}

private fun filterTypeAliases(typeAliases: List<DTypeAlias>) =
typeAliases.filter { it.isAllowedInPackage() }.let {
Pair(typeAliases.size != it.size, it)
}

private fun filterClasslikes(
classlikeList: List<DClasslike>
): Pair<Boolean, List<DClasslike>> {
var modified = false
return classlikeList.filter { classlike ->
when (classlike) {
is DClass -> classlike.isAllowedInPackage()
is DInterface -> classlike.isAllowedInPackage()
is DEnum -> classlike.isAllowedInPackage()
is DObject -> classlike.isAllowedInPackage()
is DAnnotation -> classlike.isAllowedInPackage()
}
}.map { classlike ->
fun helper(): DClasslike = when (classlike) {
is DClass -> classlike.copy(
constructors = filterFunctions(classlike.constructors).let {
modified = modified || it.first; it.second
},
functions = filterFunctions(classlike.functions).let {
modified = modified || it.first; it.second
},
properties = filterProperties(classlike.properties).let {
modified = modified || it.first; it.second
},
classlikes = filterClasslikes(classlike.classlikes).let {
modified = modified || it.first; it.second
}
)
is DAnnotation -> classlike.copy(
functions = filterFunctions(classlike.functions).let {
modified = modified || it.first; it.second
},
properties = filterProperties(classlike.properties).let {
modified = modified || it.first; it.second
},
classlikes = filterClasslikes(classlike.classlikes).let {
modified = modified || it.first; it.second
},
constructors = filterFunctions(classlike.constructors).let {
modified = modified || it.first; it.second
}
)
is DEnum -> classlike.copy(
entries = filterEnumEntries(classlike.entries),
functions = filterFunctions(classlike.functions).let {
modified = modified || it.first; it.second
},
properties = filterProperties(classlike.properties).let {
modified = modified || it.first; it.second
},
classlikes = filterClasslikes(classlike.classlikes).let {
modified = modified || it.first; it.second
},
constructors = filterFunctions(classlike.constructors).let {
modified = modified || it.first; it.second
},
)
is DInterface -> classlike.copy(
functions = filterFunctions(classlike.functions).let {
modified = modified || it.first; it.second
},
properties = filterProperties(classlike.properties).let {
modified = modified || it.first; it.second
},
classlikes = filterClasslikes(classlike.classlikes).let {
modified = modified || it.first; it.second
}
)
is DObject -> classlike.copy(
functions = filterFunctions(classlike.functions).let {
modified = modified || it.first; it.second
},
properties = filterProperties(classlike.properties).let {
modified = modified || it.first; it.second
},
classlikes = filterClasslikes(classlike.classlikes).let {
modified = modified || it.first; it.second
}
)
}
helper()
}.let {
Pair(it.size != classlikeList.size || modified, it)
}
}
private fun Annotations.Annotation.isHidden(): Boolean {
val level = (this.params["level"] as? EnumValue) ?: return false
return level.enumName == "DeprecationLevel.HIDDEN"
}
}
80 changes: 80 additions & 0 deletions plugins/base/src/test/kotlin/filter/DeprecationFilterTest.kt
Expand Up @@ -7,6 +7,47 @@ import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test

class DeprecationFilterTest : BaseAbstractTest() {

@Test
fun `should skip hidden deprecated level regardless of skipDeprecated`() {
val configuration = dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/main/kotlin/basic/Test.kt")
skipDeprecated = false
perPackageOptions = mutableListOf(
PackageOptionsImpl(
"example.*",
true,
false,
false,
false,
DokkaDefaults.documentedVisibilities
)
)
}
}
}

testInline(
"""
|/src/main/kotlin/basic/Test.kt
|package example
|
|@Deprecated("dep", level = DeprecationLevel.HIDDEN)
|fun testFunction() { }
|
""".trimMargin(),
configuration
) {
preMergeDocumentablesTransformationStage = {
Assertions.assertTrue(
it.first().packages.first().functions.isEmpty()
)
}
}
}

@Test
fun `function with false global skipDeprecated`() {
val configuration = dokkaConfiguration {
Expand Down Expand Up @@ -37,6 +78,7 @@ class DeprecationFilterTest : BaseAbstractTest() {
}
}
}

@Test
fun `deprecated function with false global skipDeprecated`() {
val configuration = dokkaConfiguration {
Expand Down Expand Up @@ -67,6 +109,7 @@ class DeprecationFilterTest : BaseAbstractTest() {
}
}
}

@Test
fun `deprecated function with true global skipDeprecated`() {
val configuration = dokkaConfiguration {
Expand Down Expand Up @@ -97,6 +140,42 @@ class DeprecationFilterTest : BaseAbstractTest() {
}
}
}

@Test
fun `should skip deprecated companion object`() {
val configuration = dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/main/kotlin/basic/Test.kt")
skipDeprecated = true
}
}
}

testInline(
"""
|/src/main/kotlin/basic/Test.kt
|package example
|
|class Test {
| @Deprecated("dep")
| companion object {
| fun method() {}
| }
|}
|
|
""".trimMargin(),
configuration
) {
preMergeDocumentablesTransformationStage = {
Assertions.assertTrue(
it.first().packages.first().classlikes.first().classlikes.isEmpty()
)
}
}
}

@Test
fun `deprecated function with false global true package skipDeprecated`() {
val configuration = dokkaConfiguration {
Expand Down Expand Up @@ -137,6 +216,7 @@ class DeprecationFilterTest : BaseAbstractTest() {
}
}
}

@Test
fun `deprecated function with true global false package skipDeprecated`() {
val configuration = dokkaConfiguration {
Expand Down