aboutsummaryrefslogtreecommitdiff
path: root/plugins/base
diff options
context:
space:
mode:
authorAndrey Tyrin <andrei.tyrin@jetbrains.com>2023-01-12 20:03:06 +0100
committerGitHub <noreply@github.com>2023-01-12 20:03:06 +0100
commit303c937a7c33fa9df5c28079c423ee071e87e410 (patch)
tree6849bd6114d2711fe49be4d9f06a7fc089399c38 /plugins/base
parent8d1536bdbadbab7e1b89902b0cc920c36a6103ad (diff)
downloaddokka-303c937a7c33fa9df5c28079c423ee071e87e410.tar.gz
dokka-303c937a7c33fa9df5c28079c423ee071e87e410.tar.bz2
dokka-303c937a7c33fa9df5c28079c423ee071e87e410.zip
Default Java constructor (#2795)
Diffstat (limited to 'plugins/base')
-rw-r--r--plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt77
-rw-r--r--plugins/base/src/test/kotlin/model/JavaTest.kt13
-rw-r--r--plugins/base/src/test/kotlin/signatures/InheritedAccessorsSignatureTest.kt48
-rw-r--r--plugins/base/src/test/kotlin/signatures/SignatureTest.kt8
-rw-r--r--plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt90
5 files changed, 196 insertions, 40 deletions
diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt
index 574fb2e8..f67ee15c 100644
--- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt
+++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt
@@ -16,14 +16,15 @@ import org.jetbrains.dokka.analysis.KotlinAnalysis
import org.jetbrains.dokka.analysis.PsiDocumentableSource
import org.jetbrains.dokka.analysis.from
import org.jetbrains.dokka.base.DokkaBase
-import org.jetbrains.dokka.base.translators.psi.parsers.JavaDocumentationParser
import org.jetbrains.dokka.base.translators.psi.parsers.JavadocParser
import org.jetbrains.dokka.base.translators.typeConstructorsBeingExceptions
import org.jetbrains.dokka.base.translators.unquotedValue
-import org.jetbrains.dokka.links.*
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.nextTarget
+import org.jetbrains.dokka.links.withClass
+import org.jetbrains.dokka.links.withEnumEntryExtra
import org.jetbrains.dokka.model.*
import org.jetbrains.dokka.model.AnnotationTarget
-import org.jetbrains.dokka.model.Nullable
import org.jetbrains.dokka.model.doc.DocumentationNode
import org.jetbrains.dokka.model.doc.Param
import org.jetbrains.dokka.model.properties.PropertyContainer
@@ -45,7 +46,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.io.File
class DefaultPsiToDocumentableTranslator(
- context: DokkaContext
+ context: DokkaContext,
) : AsyncSourceToDocumentableTranslator {
private val kotlinAnalysis: KotlinAnalysis = context.plugin<DokkaBase>().querySingle { kotlinAnalysis }
@@ -94,8 +95,8 @@ class DefaultPsiToDocumentableTranslator(
class DokkaPsiParser(
private val sourceSetData: DokkaSourceSet,
- facade: DokkaResolutionFacade,
- private val logger: DokkaLogger
+ private val facade: DokkaResolutionFacade,
+ private val logger: DokkaLogger,
) {
private val javadocParser = JavadocParser(logger, facade)
private val syntheticDocProvider = SyntheticElementDocumentationProvider(javadocParser, facade)
@@ -266,7 +267,7 @@ class DefaultPsiToDocumentableTranslator(
}
parsedFields + parsedSuperFields
}
- val source = PsiDocumentableSource(this).toSourceSetDependent()
+ val source = parseSources()
val classlikes = async { innerClasses.asIterable().parallelMap { parseClasslike(it, dri) } }
val visibility = getVisibility().toSourceSetDependent()
val ancestors = (listOfNotNull(ancestry.superclass?.let {
@@ -276,10 +277,16 @@ class DefaultPsiToDocumentableTranslator(
JavaClassKindTypes.CLASS
)
}
- }) + ancestry.interfaces.map { TypeConstructorWithKind(it.typeConstructor, JavaClassKindTypes.INTERFACE) }).toSourceSetDependent()
+ }) + ancestry.interfaces.map {
+ TypeConstructorWithKind(
+ it.typeConstructor,
+ JavaClassKindTypes.INTERFACE
+ )
+ }).toSourceSetDependent()
val modifiers = getModifier().toSourceSetDependent()
val implementedInterfacesExtra =
ImplementedInterfaces(ancestry.allImplementedInterfaces().toSourceSetDependent())
+
when {
isAnnotationType ->
DAnnotation(
@@ -293,7 +300,7 @@ class DefaultPsiToDocumentableTranslator(
classlikes = classlikes.await(),
visibility = visibility,
companion = null,
- constructors = constructors.map { parseFunction(it, true) },
+ constructors = parseConstructors(),
generics = mapTypeParameters(dri),
sourceSets = setOf(sourceSetData),
isExpectActual = false,
@@ -303,6 +310,7 @@ class DefaultPsiToDocumentableTranslator(
.toAnnotations()
)
)
+
isEnum -> DEnum(
dri = dri,
name = name.orEmpty(),
@@ -327,11 +335,12 @@ class DefaultPsiToDocumentableTranslator(
expectPresentInSet = null,
sources = source,
functions = allFunctions.await(),
- properties = fields.filter { it !is PsiEnumConstant }.map { parseField(it, accessors[it].orEmpty()) },
+ properties = fields.filter { it !is PsiEnumConstant }
+ .map { parseField(it, accessors[it].orEmpty()) },
classlikes = classlikes.await(),
visibility = visibility,
companion = null,
- constructors = constructors.map { parseFunction(it, true) },
+ constructors = parseConstructors(),
supertypes = ancestors,
sourceSets = setOf(sourceSetData),
isExpectActual = false,
@@ -341,6 +350,7 @@ class DefaultPsiToDocumentableTranslator(
.toAnnotations()
)
)
+
isInterface -> DInterface(
dri = dri,
name = name.orEmpty(),
@@ -362,10 +372,11 @@ class DefaultPsiToDocumentableTranslator(
.toAnnotations()
)
)
+
else -> DClass(
dri = dri,
name = name.orEmpty(),
- constructors = constructors.map { parseFunction(it, true) },
+ constructors = parseConstructors(),
functions = allFunctions.await(),
properties = allFields.await(),
classlikes = classlikes.await(),
@@ -390,14 +401,38 @@ class DefaultPsiToDocumentableTranslator(
}
}
+ private fun PsiClass.parseConstructors(): List<DFunction> {
+ val constructors = when {
+ isAnnotationType || isInterface -> emptyArray()
+ isEnum -> this.constructors
+ else -> this.constructors.takeIf { it.isNotEmpty() } ?: arrayOf(createDefaultConstructor())
+ }
+ return constructors.map { parseFunction(it, true) }
+ }
+
+ /**
+ * PSI doesn't return a default constructor if class doesn't contain an explicit one.
+ * This method create synthetic constructor
+ * Visibility modifier is preserved from the class.
+ */
+ private fun PsiClass.createDefaultConstructor(): PsiMethod {
+ val psiElementFactory = JavaPsiFacade.getElementFactory(facade.project)
+ val signature = when (val classVisibility = getVisibility()) {
+ JavaVisibility.Default -> name.orEmpty()
+ else -> "${classVisibility.name} $name"
+ }
+ return psiElementFactory.createConstructor(signature, this)
+ }
+
private fun AncestryNode.exceptionInSupertypesOrNull(): ExceptionInSupertypes? =
- typeConstructorsBeingExceptions().takeIf { it.isNotEmpty() }?.let { ExceptionInSupertypes(it.toSourceSetDependent()) }
+ typeConstructorsBeingExceptions().takeIf { it.isNotEmpty() }
+ ?.let { ExceptionInSupertypes(it.toSourceSetDependent()) }
private fun parseFunction(
psi: PsiMethod,
isConstructor: Boolean = false,
inheritedFrom: DRI? = null,
- parentDRI: DRI? = null
+ parentDRI: DRI? = null,
): DFunction {
val dri = parentDRI?.let { dri ->
DRI.from(psi).copy(packageName = dri.packageName, classNames = dri.classNames)
@@ -427,7 +462,7 @@ class DefaultPsiToDocumentableTranslator(
},
documentation = docs.toSourceSetDependent(),
expectPresentInSet = null,
- sources = PsiDocumentableSource(psi).toSourceSetDependent(),
+ sources = psi.parseSources(),
visibility = psi.getVisibility().toSourceSetDependent(),
type = psi.returnType?.let { getBound(type = it) } ?: Void,
generics = psi.mapTypeParameters(dri),
@@ -450,6 +485,16 @@ class DefaultPsiToDocumentableTranslator(
)
}
+ private fun PsiNamedElement.parseSources(): SourceSetDependent<DocumentableSource> {
+ return when {
+ // `isPhysical` detects the virtual declarations without real sources.
+ // Otherwise, `PsiDocumentableSource` initialization will fail: non-physical declarations doesn't have `virtualFile`.
+ // This check protects from accidentally requesting sources for synthetic / virtual declarations.
+ isPhysical -> PsiDocumentableSource(this).toSourceSetDependent()
+ else -> emptyMap()
+ }
+ }
+
private fun PsiMethod.getDocumentation(): DocumentationNode =
this.takeIf { it is SyntheticElement }?.let { syntheticDocProvider.getDocumentation(it) }
?: javadocParser.parseDocumentation(this)
@@ -657,7 +702,7 @@ class DefaultPsiToDocumentableTranslator(
name = psi.name,
documentation = javadocParser.parseDocumentation(psi).toSourceSetDependent(),
expectPresentInSet = null,
- sources = PsiDocumentableSource(psi).toSourceSetDependent(),
+ sources = psi.parseSources(),
visibility = psi.getVisibility(getter).toSourceSetDependent(),
type = getBound(psi.type),
receiver = null,
diff --git a/plugins/base/src/test/kotlin/model/JavaTest.kt b/plugins/base/src/test/kotlin/model/JavaTest.kt
index 47a25943..b3ee2726 100644
--- a/plugins/base/src/test/kotlin/model/JavaTest.kt
+++ b/plugins/base/src/test/kotlin/model/JavaTest.kt
@@ -13,7 +13,6 @@ import utils.AbstractModelTest
import utils.assertNotNull
import utils.name
import kotlin.test.assertEquals
-import org.jetbrains.dokka.links.Callable as DRICallable
class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
val configuration = dokkaConfiguration {
@@ -48,7 +47,7 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
) {
with((this / "java" / "Test").cast<DClass>()) {
name equals "Test"
- children counts 1
+ children counts 2 // default constructor and function
with((this / "fn").cast<DFunction>()) {
name equals "fn"
val params = parameters.map { it.documentation.values.first().children.first() as Param }
@@ -118,7 +117,7 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
) {
with((this / "java" / "Test").cast<DClass>()) {
name equals "Test"
- children counts 1
+ children counts 2 // default constructor and function
with((this / "arrayToString").cast<DFunction>()) {
name equals "arrayToString"
@@ -219,10 +218,10 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
""", configuration = configuration
) {
with((this / "java" / "InnerClass").cast<DClass>()) {
- children counts 1
+ children counts 2 // default constructor and inner class
with((this / "D").cast<DClass>()) {
name equals "D"
- children counts 0
+ children counts 1 // default constructor
}
}
}
@@ -239,7 +238,7 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
) {
with((this / "java" / "Foo").cast<DClass>()) {
name equals "Foo"
- children counts 1
+ children counts 2 // default constructor and function
with((this / "bar").cast<DFunction>()) {
name equals "bar"
@@ -263,7 +262,7 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
""", configuration = configuration
) {
with((this / "java" / "Test").cast<DClass>()) {
- children counts 2
+ children counts 3 // default constructor + 2 props
with((this / "i").cast<DProperty>()) {
getter equals null
diff --git a/plugins/base/src/test/kotlin/signatures/InheritedAccessorsSignatureTest.kt b/plugins/base/src/test/kotlin/signatures/InheritedAccessorsSignatureTest.kt
index a75c00fc..49a70f1c 100644
--- a/plugins/base/src/test/kotlin/signatures/InheritedAccessorsSignatureTest.kt
+++ b/plugins/base/src/test/kotlin/signatures/InheritedAccessorsSignatureTest.kt
@@ -62,11 +62,11 @@ class InheritedAccessorsSignatureTest : BaseAbstractTest() {
writerPlugin.writer.renderedContent("root/test/-a/index.html").let { javaClassContent ->
val signatures = javaClassContent.signature().toList()
assertEquals(
- 2, signatures.size,
- "Expected 2 signatures: class signature and property"
+ 3, signatures.size,
+ "Expected 3 signatures: class signature, default constructor and property"
)
- val property = signatures[1]
+ val property = signatures[2]
property.match(
"open var ", A("a"), ":", A("Int"),
ignoreSpanWithTokenStyle = true
@@ -109,9 +109,13 @@ class InheritedAccessorsSignatureTest : BaseAbstractTest() {
writerPlugin.writer.renderedContent("root/test/-a/index.html").let { javaClassContent ->
val signatures = javaClassContent.signature().toList()
- assertEquals(2, signatures.size, "Expected 2 signatures: class signature and property")
+ assertEquals(
+ 3,
+ signatures.size,
+ "Expected 3 signatures: class signature, default constructor and property"
+ )
- val property = signatures[1]
+ val property = signatures[2]
property.match(
"open val ", A("a"), ":", A("Int"),
ignoreSpanWithTokenStyle = true
@@ -156,9 +160,13 @@ class InheritedAccessorsSignatureTest : BaseAbstractTest() {
writerPlugin.writer.renderedContent("root/test/-a/index.html").let { javaClassContent ->
val signatures = javaClassContent.signature().toList()
- assertEquals(2, signatures.size, "Expected 2 signatures: class signature and setter")
+ assertEquals(
+ 3,
+ signatures.size,
+ "Expected 3 signatures: class signature, default constructor and setter"
+ )
- val setterFunction = signatures[1]
+ val setterFunction = signatures[2]
setterFunction.match(
"open fun ", A("setA"), "(", Parameters(
Parameter("a: ", A("Int"))
@@ -241,9 +249,13 @@ class InheritedAccessorsSignatureTest : BaseAbstractTest() {
renderingStage = { _, _ ->
writerPlugin.writer.renderedContent("root/test/-java-class/index.html").let { kotlinClassContent ->
val signatures = kotlinClassContent.signature().toList()
- assertEquals(2, signatures.size, "Expected to find two signatures: class and property")
+ assertEquals(
+ 3,
+ signatures.size,
+ "Expected to find 3 signatures: class, default constructor and property"
+ )
- val property = signatures[1]
+ val property = signatures[2]
property.match(
"open var ", A("variable"), ": ", Span("String"),
ignoreSpanWithTokenStyle = true
@@ -290,15 +302,19 @@ class InheritedAccessorsSignatureTest : BaseAbstractTest() {
// test added to control changes
writerPlugin.writer.renderedContent("root/test/-java-class/index.html").let { javaClassContent ->
val signatures = javaClassContent.signature().toList()
- assertEquals(3, signatures.size, "Expected to find 3 signatures: class and two accessors")
+ assertEquals(
+ 4,
+ signatures.size,
+ "Expected to find 4 signatures: class, default constructor and two accessors"
+ )
- val getter = signatures[1]
+ val getter = signatures[2]
getter.match(
"fun ", A("getVariable"), "(): ", Span("String"),
ignoreSpanWithTokenStyle = true
)
- val setter = signatures[2]
+ val setter = signatures[3]
setter.match(
"fun ", A("setVariable"), "(", Parameters(
Parameter("value: ", Span("String"))
@@ -367,9 +383,13 @@ class InheritedAccessorsSignatureTest : BaseAbstractTest() {
writerPlugin.writer.renderedContent("root/test/-java-class/index.html").let { javaClassContent ->
val signatures = javaClassContent.signature().toList()
- assertEquals(2, signatures.size, "Expected 2 signatures: class signature and property")
+ assertEquals(
+ 3,
+ signatures.size,
+ "Expected 3 signatures: class signature, default constructor and property"
+ )
- val property = signatures[1]
+ val property = signatures[2]
property.match(
"protected open var ", A("protectedGetterAndProtectedSetter"), ":", A("Int"),
ignoreSpanWithTokenStyle = true
diff --git a/plugins/base/src/test/kotlin/signatures/SignatureTest.kt b/plugins/base/src/test/kotlin/signatures/SignatureTest.kt
index 77c92d9c..f017c815 100644
--- a/plugins/base/src/test/kotlin/signatures/SignatureTest.kt
+++ b/plugins/base/src/test/kotlin/signatures/SignatureTest.kt
@@ -976,9 +976,13 @@ class SignatureTest : BaseAbstractTest() {
writerPlugin.writer.renderedContent("root/test/-java-class/index.html").let { kotlinClassContent ->
val signatures = kotlinClassContent.signature().toList()
- assertEquals(2, signatures.size, "Expected 2 signatures: class signature and property")
+ assertEquals(
+ 3,
+ signatures.size,
+ "Expected 3 signatures: class signature, default constructor and property"
+ )
- val property = signatures[1]
+ val property = signatures[2]
property.match(
"open var ", A("property"), ":", A("Int"),
ignoreSpanWithTokenStyle = true
diff --git a/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt b/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt
index 711b9c02..537a4bfc 100644
--- a/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt
+++ b/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt
@@ -8,7 +8,6 @@ import org.jetbrains.dokka.links.PointingToDeclaration
import org.jetbrains.dokka.model.*
import org.jetbrains.dokka.model.doc.*
import org.jetbrains.dokka.plugability.DokkaPlugin
-import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import utils.assertNotNull
@@ -741,4 +740,93 @@ class DefaultPsiToDocumentableTranslatorTest : BaseAbstractTest() {
}
}
}
+
+ @Test
+ fun `should have public default constructor in public class`() {
+ testInline(
+ """
+ |/src/main/java/test/A.java
+ |package test;
+ |public class A {
+ |}
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ val testedClass = module.findClasslike(packageName = "test", "A") as DClass
+
+ assertEquals(1, testedClass.constructors.size, "Expect 1 default constructor")
+ assertTrue(
+ testedClass.constructors.first().parameters.isEmpty(),
+ "Expect default constructor doesn't have params"
+ )
+ assertEquals(JavaVisibility.Public, testedClass.constructors.first().visibility())
+ }
+ }
+ }
+
+ @Test
+ fun `should have package-private default constructor in package-private class`() {
+ testInline(
+ """
+ |/src/main/java/test/A.java
+ |package test;
+ |class A {
+ |}
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ val testedClass = module.findClasslike(packageName = "test", "A") as DClass
+
+ assertEquals(1, testedClass.constructors.size, "Expect 1 default constructor")
+ assertEquals(JavaVisibility.Default, testedClass.constructors.first().visibility())
+ }
+ }
+ }
+
+ @Test
+ fun `should have private default constructor in private nested class`() {
+ testInline(
+ """
+ |/src/main/java/test/A.java
+ |package test;
+ |class A {
+ | private static class PrivateNested{}
+ |}
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ val parentClass = module.findClasslike(packageName = "test", "A") as DClass
+ val testedClass = parentClass.classlikes.single { it.name == "PrivateNested" } as DClass
+
+ assertEquals(1, testedClass.constructors.size, "Expect 1 default constructor")
+ assertEquals(JavaVisibility.Private, testedClass.constructors.first().visibility())
+ }
+ }
+ }
+
+ @Test
+ fun `should not have a default constructor because have explicit private`() {
+ testInline(
+ """
+ |/src/main/java/test/A.java
+ |package test;
+ |public class A {
+ | private A(){}
+ |}
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ val testedClass = module.findClasslike(packageName = "test", "A") as DClass
+
+ assertEquals(1, testedClass.constructors.size, "Expect 1 declared constructor")
+ assertEquals(JavaVisibility.Private, testedClass.constructors.first().visibility())
+ }
+ }
+ }
}
+
+private fun DFunction.visibility() = visibility.values.first()