Skip to content

Commit

Permalink
Don't suppress obvious members in kotlin.Enum and kotlin.Any (#3349)
Browse files Browse the repository at this point in the history
  • Loading branch information
whyoleg committed Nov 24, 2023
1 parent 17ad866 commit 417b17b
Show file tree
Hide file tree
Showing 5 changed files with 391 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package org.jetbrains.dokka.analysis.test

import org.junit.jupiter.api.Tag

// COPY OF dokka-subprojects/plugin-base/src/test/kotlin/utils/TagsAnnotations.kt

/**
* Run a test only for descriptors, not symbols.
*
* In theory, these tests can be fixed
*/
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(
AnnotationRetention.RUNTIME
)
@Tag("onlyDescriptors")
annotation class OnlyDescriptors(val reason: String = "")

/**
* Run a test only for descriptors, not symbols.
*
* These tests cannot be fixed until Analysis API does not support MPP
*/
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(
AnnotationRetention.RUNTIME
)
@Tag("onlyDescriptorsMPP")
annotation class OnlyDescriptorsMPP(val reason: String = "")
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
/*
* Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package org.jetbrains.dokka.analysis.test.documentable

import org.jetbrains.dokka.analysis.test.OnlyDescriptors
import org.jetbrains.dokka.analysis.test.api.kotlinJvmTestProject
import org.jetbrains.dokka.analysis.test.api.parse
import org.jetbrains.dokka.analysis.test.api.useServices
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.DFunction
import org.jetbrains.dokka.model.ObviousMember
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

class ObviousFunctionsTest {

@OnlyDescriptors("#3354")
@Test
fun `kotlin_Any should not have obvious members`() {
val project = kotlinJvmTestProject {
ktFile("kotlin/Any.kt") {
+"""
package kotlin
public open class Any {
public open fun equals(other: Any?): Boolean
public open fun hashCode(): Int
public open fun toString(): String
}
"""
}
}

val module = project.parse()

val pkg = module.packages.single()
val any = pkg.classlikes.single()

assertEquals("Any", any.name)

assertObviousFunctions(
expectedObviousFunctions = emptySet(),
expectedNonObviousFunctions = setOf("equals", "hashCode", "toString"),
actualFunctions = any.functions
)
}

@Test
fun `kotlin_Any should not have obvious members via external documentable provider`() {
val project = kotlinJvmTestProject {
ktFile("SomeClass.kt") {
+"class SomeClass"
}
}

project.useServices {
val any = externalDocumentableProvider.getClasslike(
DRI("kotlin", "Any"),
it.context.configuration.sourceSets.single()
)
assertNotNull(any)
assertEquals("Any", any.name)

assertObviousFunctions(
expectedObviousFunctions = emptySet(),
expectedNonObviousFunctions = setOf("equals", "hashCode", "toString"),
actualFunctions = any.functions
)
}
}

// when running with K2 - inherited from java enum functions available: "clone", "finalize", "getDeclaringClass"
@OnlyDescriptors("#3196")
@Test
fun `kotlin_Enum should not have obvious members`() {
val project = kotlinJvmTestProject {
ktFile("kotlin/Any.kt") {
+"""
package kotlin
public abstract class Enum<E : Enum<E>>(name: String, ordinal: Int): Comparable<E> {
public override final fun compareTo(other: E): Int
public override final fun equals(other: Any?): Boolean
public override final fun hashCode(): Int
public override fun toString(): String
}
"""
}
}

val module = project.parse()

val pkg = module.packages.single()
val any = pkg.classlikes.single()

assertEquals("Enum", any.name)

assertObviousFunctions(
expectedObviousFunctions = emptySet(),
expectedNonObviousFunctions = setOf("compareTo", "equals", "hashCode", "toString"),
actualFunctions = any.functions
)
}

// when running with K2 there is no equals, hashCode, toString present
@OnlyDescriptors("#3196")
@Test
fun `kotlin_Enum should not have obvious members via external documentable provider`() {
val project = kotlinJvmTestProject {
ktFile("SomeClass.kt") {
+"class SomeClass"
}
}

project.useServices {
val enum = externalDocumentableProvider.getClasslike(
DRI("kotlin", "Enum"),
it.context.configuration.sourceSets.single()
)
assertNotNull(enum)
assertEquals("Enum", enum.name)

assertObviousFunctions(
expectedObviousFunctions = emptySet(),
expectedNonObviousFunctions = setOf(
"compareTo", "equals", "hashCode", "toString",
// inherited from java enum
"clone", "finalize", "getDeclaringClass"
),
actualFunctions = enum.functions
)
}
}

@Test
fun `should mark only toString, equals and hashcode as obvious for class`() {
val project = kotlinJvmTestProject {
ktFile("SomeClass.kt") {
+"""
class SomeClass {
fun custom() {}
}
"""
}
}

val module = project.parse()

val pkg = module.packages.single()
val cls = pkg.classlikes.single()

assertEquals("SomeClass", cls.name)

assertObviousFunctions(
expectedObviousFunctions = setOf("equals", "hashCode", "toString"),
expectedNonObviousFunctions = setOf("custom"),
actualFunctions = cls.functions
)
}

@Test
fun `should mark only toString, equals and hashcode as obvious for interface`() {
val project = kotlinJvmTestProject {
ktFile("SomeClass.kt") {
+"""
interface SomeClass {
fun custom()
}
"""
}
}

val module = project.parse()

val pkg = module.packages.single()
val cls = pkg.classlikes.single()

assertEquals("SomeClass", cls.name)

assertObviousFunctions(
expectedObviousFunctions = setOf("equals", "hashCode", "toString"),
expectedNonObviousFunctions = setOf("custom"),
actualFunctions = cls.functions
)
}

@Test
fun `should mark data class generated functions as obvious`() {
val project = kotlinJvmTestProject {
ktFile("SomeClass.kt") {
+"""
data class SomeClass(val x: String) {
fun custom() {}
}
"""
}
}

val module = project.parse()

val pkg = module.packages.single()
val cls = pkg.classlikes.single()

assertEquals("SomeClass", cls.name)

assertObviousFunctions(
expectedObviousFunctions = setOf("equals", "hashCode", "toString", "component1", "copy"),
expectedNonObviousFunctions = setOf("custom"),
actualFunctions = cls.functions
)
}

@Test
fun `should not mark as obvious if override`() {
val project = kotlinJvmTestProject {
ktFile("SomeClass.kt") {
+"""
data class SomeClass(val x: String) {
override fun toString(): String = x
}
"""
}
}

val module = project.parse()

val pkg = module.packages.single()
val cls = pkg.classlikes.single()

assertEquals("SomeClass", cls.name)

assertObviousFunctions(
expectedObviousFunctions = setOf("equals", "hashCode", "component1", "copy"),
expectedNonObviousFunctions = setOf("toString"),
actualFunctions = cls.functions
)
}

private fun assertObviousFunctions(
expectedObviousFunctions: Set<String>,
expectedNonObviousFunctions: Set<String>,
actualFunctions: List<DFunction>
) {
val (notObviousFunctions, obviousFunctions) = actualFunctions.partition {
it.extra[ObviousMember] == null
}

assertEquals(
expectedNonObviousFunctions,
notObviousFunctions.map { it.name }.toSet()
)

assertEquals(
expectedObviousFunctions,
obviousFunctions.map { it.name }.toSet()
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ private class DokkaDescriptorVisitor(
descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
(descriptor.getAnnotations() + descriptor.fileLevelAnnotations()).toSourceSetDependent()
.toAnnotations(),
ObviousMember.takeIf { descriptor.isObvious() },
ObviousMember.takeIf { descriptor.isObvious(inheritedFrom) },
)
)
}
Expand Down Expand Up @@ -649,15 +649,16 @@ private class DokkaDescriptorVisitor(
.takeIf { parent.dri.classNames != this.classNames || parent.dri.packageName != this.packageName }
}

private fun FunctionDescriptor.isObvious(): Boolean {
private fun FunctionDescriptor.isObvious(inheritedFrom: DRI?): Boolean {
return kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE
|| (kind == CallableMemberDescriptor.Kind.SYNTHESIZED && !syntheticDocProvider.isDocumented(this))
|| containingDeclaration.fqNameOrNull()?.isObvious() == true
|| inheritedFrom?.isObvious() == true
}

private fun FqName.isObvious(): Boolean = with(this.asString()) {
return this == "kotlin.Any" || this == "kotlin.Enum"
|| this == "java.lang.Object" || this == "java.lang.Enum"
private fun DRI.isObvious(): Boolean = when (packageName) {
"kotlin" -> classNames == "Any" || classNames == "Enum"
"java.lang" -> classNames == "Object" || classNames == "Enum"
else -> false
}

suspend fun visitConstructorDescriptor(descriptor: ConstructorDescriptor, parent: DRIWithPlatformInfo): DFunction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ internal class DokkaSymbolVisitor(
functionSymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(),
getDokkaAnnotationsFrom(functionSymbol)
?.toSourceSetDependent()?.toAnnotations(),
ObviousMember.takeIf { isObvious(functionSymbol) },
ObviousMember.takeIf { isObvious(functionSymbol, inheritedFrom) },
)
)
}
Expand Down Expand Up @@ -868,14 +868,15 @@ internal class DokkaSymbolVisitor(
else
getKDocDocumentationFrom(symbol, logger) ?: javadocParser?.let { getJavaDocDocumentationFrom(symbol, it) }

private fun KtAnalysisSession.isObvious(functionSymbol: KtFunctionSymbol): Boolean {
private fun KtAnalysisSession.isObvious(functionSymbol: KtFunctionSymbol, inheritedFrom: DRI?): Boolean {
return functionSymbol.origin == KtSymbolOrigin.SOURCE_MEMBER_GENERATED && !hasGeneratedKDocDocumentation(functionSymbol) ||
!functionSymbol.isOverride && functionSymbol.callableIdIfNonLocal?.classId?.isObvious() == true
!functionSymbol.isOverride && inheritedFrom?.isObvious() == true
}

private fun ClassId.isObvious(): Boolean = with(asString()) {
return this == "kotlin/Any" || this == "kotlin/Enum"
|| this == "java.lang/Object" || this == "java.lang/Enum"
private fun DRI.isObvious(): Boolean = when (packageName) {
"kotlin" -> classNames == "Any" || classNames == "Enum"
"java.lang" -> classNames == "Object" || classNames == "Enum"
else -> false
}

private fun KtSymbol.getSource() = KtPsiDocumentableSource(psi).toSourceSetDependent()
Expand Down

0 comments on commit 417b17b

Please sign in to comment.