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

Default Java constructor #2795

Merged
merged 4 commits into from
Jan 12, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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(
Expand All @@ -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,
Expand All @@ -303,6 +310,7 @@ class DefaultPsiToDocumentableTranslator(
.toAnnotations()
)
)

isEnum -> DEnum(
dri = dri,
name = name.orEmpty(),
Expand All @@ -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,
Expand All @@ -341,6 +350,7 @@ class DefaultPsiToDocumentableTranslator(
.toAnnotations()
)
)

isInterface -> DInterface(
dri = dri,
name = name.orEmpty(),
Expand All @@ -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(),
Expand All @@ -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)
Expand Down Expand Up @@ -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),
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 6 additions & 7 deletions plugins/base/src/test/kotlin/model/JavaTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
}
}
Expand All @@ -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"
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions plugins/base/src/test/kotlin/signatures/SignatureTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading