aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src/main/kotlin/translators
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/base/src/main/kotlin/translators')
-rw-r--r--plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt782
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/DefaultDocumentableToPageTranslator.kt17
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt525
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt474
-rw-r--r--plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt480
-rw-r--r--plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt216
6 files changed, 2494 insertions, 0 deletions
diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt
new file mode 100644
index 00000000..ffceaaa7
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt
@@ -0,0 +1,782 @@
+package org.jetbrains.dokka.base.translators.descriptors
+
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.analysis.DescriptorDocumentableSource
+import org.jetbrains.dokka.analysis.DokkaResolutionFacade
+import org.jetbrains.dokka.analysis.KotlinAnalysis
+import org.jetbrains.dokka.analysis.from
+import org.jetbrains.dokka.base.parsers.MarkdownParser
+import org.jetbrains.dokka.links.*
+import org.jetbrains.dokka.links.Callable
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.Nullable
+import org.jetbrains.dokka.model.TypeConstructor
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.transformers.sources.SourceToDocumentableTranslator
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.builtins.isExtensionFunctionType
+import org.jetbrains.kotlin.builtins.isFunctionType
+import org.jetbrains.kotlin.codegen.isJvmStaticInObjectOrClassOrInterface
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.descriptors.ClassKind
+import org.jetbrains.kotlin.descriptors.Visibility
+import org.jetbrains.kotlin.descriptors.annotations.Annotated
+import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
+import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBodies
+import org.jetbrains.kotlin.idea.kdoc.findKDoc
+import org.jetbrains.kotlin.load.kotlin.toSourceElement
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.resolve.DescriptorUtils
+import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentsInParentheses
+import org.jetbrains.kotlin.resolve.calls.components.isVararg
+import org.jetbrains.kotlin.resolve.constants.ConstantValue
+import org.jetbrains.kotlin.resolve.constants.KClassValue.Value.LocalClass
+import org.jetbrains.kotlin.resolve.constants.KClassValue.Value.NormalClass
+import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
+import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
+import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
+import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
+import org.jetbrains.kotlin.resolve.scopes.MemberScope
+import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
+import org.jetbrains.kotlin.resolve.source.PsiSourceElement
+import org.jetbrains.kotlin.types.DynamicType
+import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlin.types.TypeProjection
+import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
+import org.jetbrains.kotlin.utils.addToStdlib.safeAs
+import java.nio.file.Paths
+import org.jetbrains.kotlin.resolve.constants.AnnotationValue as ConstantsAnnotationValue
+import org.jetbrains.kotlin.resolve.constants.ArrayValue as ConstantsArrayValue
+import org.jetbrains.kotlin.resolve.constants.EnumValue as ConstantsEnumValue
+import org.jetbrains.kotlin.resolve.constants.KClassValue as ConstantsKtClassValue
+
+class DefaultDescriptorToDocumentableTranslator(
+ private val kotlinAnalysis: KotlinAnalysis
+) : SourceToDocumentableTranslator {
+
+ override fun invoke(sourceSet: DokkaSourceSet, context: DokkaContext): DModule {
+ val (environment, facade) = kotlinAnalysis[sourceSet]
+ val packageFragments = environment.getSourceFiles().asSequence()
+ .map { it.packageFqName }
+ .distinct()
+ .mapNotNull { facade.resolveSession.getPackageFragment(it) }
+ .toList()
+
+ return DokkaDescriptorVisitor(sourceSet, kotlinAnalysis[sourceSet].facade, context.logger).run {
+ packageFragments.mapNotNull { it.safeAs<PackageFragmentDescriptor>() }.map {
+ visitPackageFragmentDescriptor(
+ it,
+ DRIWithPlatformInfo(DRI.topLevel, emptyMap())
+ )
+ }
+ }.let { DModule(sourceSet.moduleDisplayName, it, emptyMap(), null, setOf(sourceSet)) }
+ }
+}
+
+data class DRIWithPlatformInfo(
+ val dri: DRI,
+ val actual: SourceSetDependent<DocumentableSource>
+)
+
+fun DRI.withEmptyInfo() = DRIWithPlatformInfo(this, emptyMap())
+
+private class DokkaDescriptorVisitor(
+ private val sourceSet: DokkaSourceSet,
+ private val resolutionFacade: DokkaResolutionFacade,
+ private val logger: DokkaLogger
+) : DeclarationDescriptorVisitorEmptyBodies<Documentable, DRIWithPlatformInfo>() {
+ override fun visitDeclarationDescriptor(descriptor: DeclarationDescriptor, parent: DRIWithPlatformInfo): Nothing {
+ throw IllegalStateException("${javaClass.simpleName} should never enter ${descriptor.javaClass.simpleName}")
+ }
+
+ private fun Collection<DeclarationDescriptor>.filterDescriptorsInSourceSet() = filter {
+ it.toSourceElement.containingFile.toString().let { path ->
+ path.isNotBlank() && sourceSet.sourceRoots.any { root ->
+ Paths.get(path).startsWith(Paths.get(root.path))
+ }
+ }
+ }
+
+ private fun <T> T.toSourceSetDependent() = mapOf(sourceSet to this)
+
+ override fun visitPackageFragmentDescriptor(
+ descriptor: PackageFragmentDescriptor,
+ parent: DRIWithPlatformInfo
+ ): DPackage {
+ val name = descriptor.fqName.asString().takeUnless { it.isBlank() } ?: fallbackPackageName()
+ val driWithPlatform = DRI(packageName = name).withEmptyInfo()
+ val scope = descriptor.getMemberScope()
+
+ return DPackage(
+ dri = driWithPlatform.dri,
+ functions = scope.functions(driWithPlatform, true),
+ properties = scope.properties(driWithPlatform, true),
+ classlikes = scope.classlikes(driWithPlatform, true),
+ typealiases = scope.typealiases(driWithPlatform, true),
+ documentation = descriptor.resolveDescriptorData(),
+ sourceSets = setOf(sourceSet)
+ )
+ }
+
+ override fun visitClassDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DClasslike =
+ when (descriptor.kind) {
+ ClassKind.ENUM_CLASS -> enumDescriptor(descriptor, parent)
+ ClassKind.OBJECT -> objectDescriptor(descriptor, parent)
+ ClassKind.INTERFACE -> interfaceDescriptor(descriptor, parent)
+ ClassKind.ANNOTATION_CLASS -> annotationDescriptor(descriptor, parent)
+ else -> classDescriptor(descriptor, parent)
+ }
+
+ private fun interfaceDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DInterface {
+ val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo()
+ val scope = descriptor.unsubstitutedMemberScope
+ val isExpect = descriptor.isExpect
+ val info = descriptor.resolveClassDescriptionData()
+
+ return DInterface(
+ dri = driWithPlatform.dri,
+ name = descriptor.name.asString(),
+ functions = scope.functions(driWithPlatform),
+ properties = scope.properties(driWithPlatform),
+ classlikes = scope.classlikes(driWithPlatform),
+ sources = descriptor.createSources(),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ supertypes = info.supertypes.toSourceSetDependent(),
+ documentation = info.docs,
+ generics = descriptor.declaredTypeParameters.map { it.toTypeParameter() },
+ companion = descriptor.companion(driWithPlatform),
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations(),
+ ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent())
+ )
+ )
+ }
+
+ private fun objectDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DObject {
+ val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo()
+ val scope = descriptor.unsubstitutedMemberScope
+ val isExpect = descriptor.isExpect
+ val info = descriptor.resolveClassDescriptionData()
+
+
+ return DObject(
+ dri = driWithPlatform.dri,
+ name = descriptor.name.asString(),
+ functions = scope.functions(driWithPlatform),
+ properties = scope.properties(driWithPlatform),
+ classlikes = scope.classlikes(driWithPlatform),
+ sources = descriptor.createSources(),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ supertypes = info.supertypes.toSourceSetDependent(),
+ documentation = info.docs,
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations(),
+ ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent())
+ )
+ )
+ }
+
+ private fun enumDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DEnum {
+ val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo()
+ val scope = descriptor.unsubstitutedMemberScope
+ val isExpect = descriptor.isExpect
+ val info = descriptor.resolveClassDescriptionData()
+
+ return DEnum(
+ dri = driWithPlatform.dri,
+ name = descriptor.name.asString(),
+ entries = scope.enumEntries(driWithPlatform),
+ constructors = descriptor.constructors.map { visitConstructorDescriptor(it, driWithPlatform) },
+ functions = scope.functions(driWithPlatform),
+ properties = scope.properties(driWithPlatform),
+ classlikes = scope.classlikes(driWithPlatform),
+ sources = descriptor.createSources(),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ supertypes = info.supertypes.toSourceSetDependent(),
+ documentation = info.docs,
+ companion = descriptor.companion(driWithPlatform),
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations(),
+ ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent())
+ )
+ )
+ }
+
+ private fun enumEntryDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DEnumEntry {
+ val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo()
+ val scope = descriptor.unsubstitutedMemberScope
+ val isExpect = descriptor.isExpect
+
+ return DEnumEntry(
+ dri = driWithPlatform.dri,
+ name = descriptor.name.asString(),
+ documentation = descriptor.resolveDescriptorData(),
+ classlikes = scope.classlikes(driWithPlatform),
+ functions = scope.functions(driWithPlatform),
+ properties = scope.properties(driWithPlatform),
+ sourceSets = setOf(sourceSet),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations(),
+ ConstructorValues(descriptor.getAppliedConstructorParameters().toSourceSetDependent())
+ )
+ )
+ }
+
+ fun annotationDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DAnnotation {
+ val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo()
+ val scope = descriptor.unsubstitutedMemberScope
+
+ return DAnnotation(
+ dri = driWithPlatform.dri,
+ name = descriptor.name.asString(),
+ documentation = descriptor.resolveDescriptorData(),
+ classlikes = scope.classlikes(driWithPlatform),
+ functions = scope.functions(driWithPlatform),
+ properties = scope.properties(driWithPlatform),
+ expectPresentInSet = null,
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations()
+ ),
+ companion = descriptor.companionObjectDescriptor?.let { objectDescriptor(it, driWithPlatform) },
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ generics = descriptor.declaredTypeParameters.map { it.toTypeParameter() },
+ constructors = descriptor.constructors.map { visitConstructorDescriptor(it, driWithPlatform) },
+ sources = descriptor.createSources()
+ )
+ }
+
+ private fun classDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DClass {
+ val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo()
+ val scope = descriptor.unsubstitutedMemberScope
+ val isExpect = descriptor.isExpect
+ val info = descriptor.resolveClassDescriptionData()
+ val actual = descriptor.createSources()
+
+ return DClass(
+ dri = driWithPlatform.dri,
+ name = descriptor.name.asString(),
+ constructors = descriptor.constructors.map {
+ visitConstructorDescriptor(
+ it,
+ if (it.isPrimary) DRIWithPlatformInfo(driWithPlatform.dri, actual)
+ else DRIWithPlatformInfo(driWithPlatform.dri, emptyMap())
+ )
+ },
+ functions = scope.functions(driWithPlatform),
+ properties = scope.properties(driWithPlatform),
+ classlikes = scope.classlikes(driWithPlatform),
+ sources = actual,
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ supertypes = info.supertypes.toSourceSetDependent(),
+ generics = descriptor.declaredTypeParameters.map { it.toTypeParameter() },
+ documentation = info.docs,
+ modifier = descriptor.modifier().toSourceSetDependent(),
+ companion = descriptor.companion(driWithPlatform),
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations(),
+ ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent())
+ )
+ )
+ }
+
+ override fun visitPropertyDescriptor(descriptor: PropertyDescriptor, parent: DRIWithPlatformInfo): DProperty {
+ val dri = parent.dri.copy(callable = Callable.from(descriptor))
+ val isExpect = descriptor.isExpect
+
+ val actual = descriptor.createSources()
+ return DProperty(
+ dri = dri,
+ name = descriptor.name.asString(),
+ receiver = descriptor.extensionReceiverParameter?.let {
+ visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actual))
+ },
+ sources = actual,
+ getter = descriptor.accessors.filterIsInstance<PropertyGetterDescriptor>().singleOrNull()?.let {
+ visitPropertyAccessorDescriptor(it, descriptor, dri)
+ },
+ setter = descriptor.accessors.filterIsInstance<PropertySetterDescriptor>().singleOrNull()?.let {
+ visitPropertyAccessorDescriptor(it, descriptor, dri)
+ },
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ documentation = descriptor.resolveDescriptorData(),
+ modifier = descriptor.modifier().toSourceSetDependent(),
+ type = descriptor.returnType!!.toBound(),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ sourceSets = setOf(sourceSet),
+ generics = descriptor.typeParameters.map { it.toTypeParameter() },
+ extra = PropertyContainer.withAll(
+ (descriptor.additionalExtras() + descriptor.getAnnotationsWithBackingField()
+ .toAdditionalExtras()).toSet().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotationsWithBackingField().toSourceSetDependent().toAnnotations()
+ )
+ )
+ }
+
+ fun CallableMemberDescriptor.createDRI(wasOverridenBy: DRI? = null): Pair<DRI, DRI?> =
+ if (kind == CallableMemberDescriptor.Kind.DECLARATION || overriddenDescriptors.isEmpty())
+ Pair(DRI.from(this), wasOverridenBy)
+ else
+ overriddenDescriptors.first().createDRI(DRI.from(this))
+
+ override fun visitFunctionDescriptor(descriptor: FunctionDescriptor, parent: DRIWithPlatformInfo): DFunction {
+ val (dri, inheritedFrom) = descriptor.createDRI()
+ val isExpect = descriptor.isExpect
+
+ val actual = descriptor.createSources()
+ return DFunction(
+ dri = dri,
+ name = descriptor.name.asString(),
+ isConstructor = false,
+ receiver = descriptor.extensionReceiverParameter?.let {
+ visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actual))
+ },
+ parameters = descriptor.valueParameters.mapIndexed { index, desc ->
+ parameter(index, desc, DRIWithPlatformInfo(dri, actual))
+ },
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ sources = actual,
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ generics = descriptor.typeParameters.map { it.toTypeParameter() },
+ documentation = descriptor.takeIf { it.kind != CallableMemberDescriptor.Kind.SYNTHESIZED }?.resolveDescriptorData() ?: emptyMap(),
+ modifier = descriptor.modifier().toSourceSetDependent(),
+ type = descriptor.returnType!!.toBound(),
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(
+ InheritedFunction(inheritedFrom.toSourceSetDependent()),
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations()
+ )
+ )
+ }
+
+ override fun visitConstructorDescriptor(descriptor: ConstructorDescriptor, parent: DRIWithPlatformInfo): DFunction {
+ val dri = parent.dri.copy(callable = Callable.from(descriptor))
+ val actual = descriptor.createSources()
+ val isExpect = descriptor.isExpect
+
+ return DFunction(
+ dri = dri,
+ name = "<init>",
+ isConstructor = true,
+ receiver = descriptor.extensionReceiverParameter?.let {
+ visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actual))
+ },
+ parameters = descriptor.valueParameters.mapIndexed { index, desc ->
+ parameter(index, desc, DRIWithPlatformInfo(dri, actual))
+ },
+ sources = actual,
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ documentation = descriptor.resolveDescriptorData().let { sourceSetDependent ->
+ if (descriptor.isPrimary) {
+ sourceSetDependent.map { entry ->
+ Pair(
+ entry.key,
+ entry.value.copy(children = (entry.value.children.find { it is Constructor }?.root?.let { constructor ->
+ listOf(Description(constructor))
+ } ?: emptyList<TagWrapper>()) + entry.value.children.filterIsInstance<Param>()))
+ }.toMap()
+ } else {
+ sourceSetDependent
+ }
+ },
+ type = descriptor.returnType.toBound(),
+ modifier = descriptor.modifier().toSourceSetDependent(),
+ generics = descriptor.typeParameters.map { it.toTypeParameter() },
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll<DFunction>(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations()
+ ).let {
+ if (descriptor.isPrimary) {
+ it + PrimaryConstructorExtra
+ } else it
+ }
+ )
+ }
+
+ override fun visitReceiverParameterDescriptor(
+ descriptor: ReceiverParameterDescriptor,
+ parent: DRIWithPlatformInfo
+ ) = DParameter(
+ dri = parent.dri.copy(target = PointingToDeclaration),
+ name = null,
+ type = descriptor.type.toBound(),
+ expectPresentInSet = null,
+ documentation = descriptor.resolveDescriptorData(),
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(descriptor.getAnnotations().toSourceSetDependent().toAnnotations())
+ )
+
+ private fun visitPropertyAccessorDescriptor(
+ descriptor: PropertyAccessorDescriptor,
+ propertyDescriptor: PropertyDescriptor,
+ parent: DRI
+ ): DFunction {
+ val dri = parent.copy(callable = Callable.from(descriptor))
+ val isGetter = descriptor is PropertyGetterDescriptor
+ val isExpect = descriptor.isExpect
+
+ fun PropertyDescriptor.asParameter(parent: DRI) =
+ DParameter(
+ parent.copy(target = PointingToCallableParameters(parameterIndex = 1)),
+ this.name.asString(),
+ type = this.type.toBound(),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ documentation = descriptor.resolveDescriptorData(),
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ getAnnotationsWithBackingField().toSourceSetDependent().toAnnotations()
+ )
+ )
+
+ val name = run {
+ val modifier = if (isGetter) "get" else "set"
+ val rawName = propertyDescriptor.name.asString()
+ "$modifier${rawName[0].toUpperCase()}${rawName.drop(1)}"
+ }
+
+ val parameters =
+ if (isGetter) {
+ emptyList()
+ } else {
+ listOf(propertyDescriptor.asParameter(dri))
+ }
+
+ return DFunction(
+ dri,
+ name,
+ isConstructor = false,
+ parameters = parameters,
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ documentation = descriptor.resolveDescriptorData(),
+ type = descriptor.returnType!!.toBound(),
+ generics = descriptor.typeParameters.map { it.toTypeParameter() },
+ modifier = descriptor.modifier().toSourceSetDependent(),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ receiver = descriptor.extensionReceiverParameter?.let {
+ visitReceiverParameterDescriptor(
+ it,
+ DRIWithPlatformInfo(dri, descriptor.createSources())
+ )
+ },
+ sources = descriptor.createSources(),
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations()
+ )
+ )
+ }
+
+ override fun visitTypeAliasDescriptor(descriptor: TypeAliasDescriptor, parent: DRIWithPlatformInfo?) =
+ with(descriptor) {
+ DTypeAlias(
+ dri = DRI.from(this),
+ name = name.asString(),
+ type = defaultType.toBound(),
+ expectPresentInSet = null,
+ underlyingType = underlyingType.toBound().toSourceSetDependent(),
+ visibility = visibility.toDokkaVisibility().toSourceSetDependent(),
+ documentation = resolveDescriptorData(),
+ sourceSets = setOf(sourceSet)
+ )
+ }
+
+ private fun parameter(index: Int, descriptor: ValueParameterDescriptor, parent: DRIWithPlatformInfo) =
+ DParameter(
+ dri = parent.dri.copy(target = PointingToCallableParameters(index)),
+ name = descriptor.name.asString(),
+ type = descriptor.type.toBound(),
+ expectPresentInSet = null,
+ documentation = descriptor.resolveDescriptorData(),
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(listOfNotNull(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations(),
+ descriptor.getDefaultValue()?.let { DefaultValue(it) }
+ ))
+ )
+
+ private fun MemberScope.getContributedDescriptors(kindFilter: DescriptorKindFilter, shouldFilter: Boolean) =
+ getContributedDescriptors(kindFilter) { true }.let {
+ if (shouldFilter) it.filterDescriptorsInSourceSet() else it
+ }
+
+ private fun MemberScope.functions(parent: DRIWithPlatformInfo, packageLevel: Boolean = false): List<DFunction> =
+ getContributedDescriptors(DescriptorKindFilter.FUNCTIONS, packageLevel)
+ .filterIsInstance<FunctionDescriptor>()
+ .map { visitFunctionDescriptor(it, parent) }
+
+ private fun MemberScope.properties(parent: DRIWithPlatformInfo, packageLevel: Boolean = false): List<DProperty> =
+ getContributedDescriptors(DescriptorKindFilter.VALUES, packageLevel)
+ .filterIsInstance<PropertyDescriptor>()
+ .map { visitPropertyDescriptor(it, parent) }
+
+ private fun MemberScope.classlikes(parent: DRIWithPlatformInfo, packageLevel: Boolean = false): List<DClasslike> =
+ getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS, packageLevel)
+ .filter { it is ClassDescriptor && it.kind != ClassKind.ENUM_ENTRY }
+ .map { visitClassDescriptor(it as ClassDescriptor, parent) }
+
+ private fun MemberScope.typealiases(parent: DRIWithPlatformInfo, packageLevel: Boolean = false): List<DTypeAlias> =
+ getContributedDescriptors(DescriptorKindFilter.TYPE_ALIASES, packageLevel)
+ .filterIsInstance<TypeAliasDescriptor>()
+ .map { visitTypeAliasDescriptor(it, parent) }
+
+ private fun MemberScope.enumEntries(parent: DRIWithPlatformInfo): List<DEnumEntry> =
+ this.getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS) { true }
+ .filterIsInstance<ClassDescriptor>()
+ .filter { it.kind == ClassKind.ENUM_ENTRY }
+ .map { enumEntryDescriptor(it, parent) }
+
+
+ private fun DeclarationDescriptor.resolveDescriptorData(): SourceSetDependent<DocumentationNode> =
+ getDocumentation()?.toSourceSetDependent() ?: emptyMap()
+
+ private fun ClassDescriptor.resolveClassDescriptionData(): ClassInfo {
+ tailrec fun buildInheritanceInformation(
+ inheritorClass: ClassDescriptor?,
+ interfaces: List<ClassDescriptor>,
+ level: Int = 0,
+ inheritanceInformation: Set<InheritanceLevel> = emptySet()
+ ): Set<InheritanceLevel> {
+ if (inheritorClass == null && interfaces.isEmpty()) return inheritanceInformation
+
+ val updated = inheritanceInformation + InheritanceLevel(
+ level,
+ inheritorClass?.let { DRI.from(it) },
+ interfaces.map { DRI.from(it) })
+ val superInterfacesFromClass = inheritorClass?.getSuperInterfaces().orEmpty()
+ return buildInheritanceInformation(
+ inheritorClass = inheritorClass?.getSuperClassNotAny(),
+ interfaces = interfaces.flatMap { it.getSuperInterfaces() } + superInterfacesFromClass,
+ level = level + 1,
+ inheritanceInformation = updated
+ )
+ }
+ return ClassInfo(
+ buildInheritanceInformation(getSuperClassNotAny(), getSuperInterfaces()).sortedBy { it.level },
+ resolveDescriptorData()
+ )
+ }
+
+ private fun TypeParameterDescriptor.toTypeParameter() =
+ DTypeParameter(
+ DRI.from(this),
+ name.identifier,
+ resolveDescriptorData(),
+ null,
+ upperBounds.map { it.toBound() },
+ setOf(sourceSet),
+ extra = PropertyContainer.withAll(additionalExtras().toSourceSetDependent().toAdditionalModifiers())
+ )
+
+ private fun KotlinType.toBound(): Bound = when (this) {
+ is DynamicType -> Dynamic
+ else -> when (val ctor = constructor.declarationDescriptor) {
+ is TypeParameterDescriptor -> OtherParameter(
+ declarationDRI = DRI.from(ctor.containingDeclaration).withPackageFallbackTo(fallbackPackageName()),
+ name = ctor.name.asString()
+ )
+ else -> TypeConstructor(
+ DRI.from(constructor.declarationDescriptor!!), // TODO: remove '!!'
+ arguments.map { it.toProjection() },
+ if (isExtensionFunctionType) FunctionModifiers.EXTENSION
+ else if (isFunctionType) FunctionModifiers.FUNCTION
+ else FunctionModifiers.NONE
+ )
+ }.let {
+ if (isMarkedNullable) Nullable(it) else it
+ }
+ }
+
+ private fun TypeProjection.toProjection(): Projection =
+ if (isStarProjection) Star else formPossiblyVariant()
+
+ private fun TypeProjection.formPossiblyVariant(): Projection = type.fromPossiblyNullable().let {
+ when (projectionKind) {
+ org.jetbrains.kotlin.types.Variance.INVARIANT -> it
+ org.jetbrains.kotlin.types.Variance.IN_VARIANCE -> Variance(Variance.Kind.In, it)
+ org.jetbrains.kotlin.types.Variance.OUT_VARIANCE -> Variance(Variance.Kind.Out, it)
+ }
+ }
+
+ private fun KotlinType.fromPossiblyNullable(): Bound =
+ toBound().let { if (isMarkedNullable) Nullable(it) else it }
+
+ private fun DeclarationDescriptor.getDocumentation() = findKDoc().let {
+ MarkdownParser(resolutionFacade, this, logger).parseFromKDocTag(it)
+ }.takeIf { it.children.isNotEmpty() }
+
+ private fun ClassDescriptor.companion(dri: DRIWithPlatformInfo): DObject? = companionObjectDescriptor?.let {
+ objectDescriptor(it, dri)
+ }
+
+ private fun MemberDescriptor.modifier() = when (modality) {
+ Modality.FINAL -> KotlinModifier.Final
+ Modality.SEALED -> KotlinModifier.Sealed
+ Modality.OPEN -> KotlinModifier.Open
+ Modality.ABSTRACT -> KotlinModifier.Abstract
+ else -> KotlinModifier.Empty
+ }
+
+ private fun MemberDescriptor.createSources(): SourceSetDependent<DocumentableSource> =
+ DescriptorDocumentableSource(this).toSourceSetDependent()
+
+ private fun FunctionDescriptor.additionalExtras() = listOfNotNull(
+ ExtraModifiers.KotlinOnlyModifiers.Infix.takeIf { isInfix },
+ ExtraModifiers.KotlinOnlyModifiers.Inline.takeIf { isInline },
+ ExtraModifiers.KotlinOnlyModifiers.Suspend.takeIf { isSuspend },
+ ExtraModifiers.KotlinOnlyModifiers.Operator.takeIf { isOperator },
+ ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isJvmStaticInObjectOrClassOrInterface() },
+ ExtraModifiers.KotlinOnlyModifiers.TailRec.takeIf { isTailrec },
+ ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal },
+ ExtraModifiers.KotlinOnlyModifiers.Override.takeIf { DescriptorUtils.isOverride(this) }
+ ).toSet()
+
+ private fun ClassDescriptor.additionalExtras() = listOfNotNull(
+ ExtraModifiers.KotlinOnlyModifiers.Inline.takeIf { isInline },
+ ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal },
+ ExtraModifiers.KotlinOnlyModifiers.Inner.takeIf { isInner },
+ ExtraModifiers.KotlinOnlyModifiers.Data.takeIf { isData },
+ ExtraModifiers.KotlinOnlyModifiers.Fun.takeIf { isFun }
+ ).toSet()
+
+ private fun ValueParameterDescriptor.additionalExtras() = listOfNotNull(
+ ExtraModifiers.KotlinOnlyModifiers.NoInline.takeIf { isNoinline },
+ ExtraModifiers.KotlinOnlyModifiers.CrossInline.takeIf { isCrossinline },
+ ExtraModifiers.KotlinOnlyModifiers.Const.takeIf { isConst },
+ ExtraModifiers.KotlinOnlyModifiers.LateInit.takeIf { isLateInit },
+ ExtraModifiers.KotlinOnlyModifiers.VarArg.takeIf { isVararg }
+ ).toSet()
+
+ private fun TypeParameterDescriptor.additionalExtras() = listOfNotNull(
+ ExtraModifiers.KotlinOnlyModifiers.Reified.takeIf { isReified }
+ ).toSet()
+
+ private fun PropertyDescriptor.additionalExtras() = listOfNotNull(
+ ExtraModifiers.KotlinOnlyModifiers.Const.takeIf { isConst },
+ ExtraModifiers.KotlinOnlyModifiers.LateInit.takeIf { isLateInit },
+ ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isJvmStaticInObjectOrClassOrInterface() },
+ ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal },
+ ExtraModifiers.KotlinOnlyModifiers.Override.takeIf { DescriptorUtils.isOverride(this) }
+ )
+
+ private fun Annotated.getAnnotations() = annotations.mapNotNull { it.toAnnotation() }
+
+ private fun ConstantValue<*>.toValue(): AnnotationParameterValue? = when (this) {
+ is ConstantsAnnotationValue -> value.toAnnotation()?.let { AnnotationValue(it) }
+ is ConstantsArrayValue -> ArrayValue(value.mapNotNull { it.toValue() })
+ is ConstantsEnumValue -> EnumValue(
+ fullEnumEntryName(),
+ DRI(enumClassId.packageFqName.asString(), fullEnumEntryName())
+ )
+ is ConstantsKtClassValue -> when (value) {
+ is NormalClass -> (value as NormalClass).value.classId.let {
+ ClassValue(
+ it.relativeClassName.asString(),
+ DRI(it.packageFqName.asString(), it.relativeClassName.asString())
+ )
+ }
+ is LocalClass -> (value as LocalClass).type.let {
+ ClassValue(
+ it.toString(),
+ DRI.from(it.constructor.declarationDescriptor as DeclarationDescriptor)
+ )
+ }
+ }
+ else -> StringValue(toString())
+ }
+
+ private fun AnnotationDescriptor.toAnnotation(): Annotations.Annotation {
+ return Annotations.Annotation(
+ DRI.from(annotationClass as DeclarationDescriptor),
+ allValueArguments.map { it.key.asString() to it.value.toValue() }.filter {
+ it.second != null
+ }.toMap() as Map<String, AnnotationParameterValue>,
+ annotationClass!!.annotations.hasAnnotation(FqName("kotlin.annotation.MustBeDocumented"))
+ )
+ }
+
+ private fun PropertyDescriptor.getAnnotationsWithBackingField(): List<Annotations.Annotation> =
+ getAnnotations() + (backingField?.getAnnotations() ?: emptyList())
+
+ private fun List<Annotations.Annotation>.toAdditionalExtras() = mapNotNull {
+ try {
+ ExtraModifiers.valueOf(it.dri.classNames?.toLowerCase() ?: "")
+ } catch (e: IllegalArgumentException) {
+ null
+ }
+ }
+
+
+ private fun ValueParameterDescriptor.getDefaultValue(): String? =
+ (source as? KotlinSourceElement)?.psi?.children?.find { it is KtExpression }?.text
+
+ private fun ClassDescriptor.getAppliedConstructorParameters() =
+ (source as PsiSourceElement).psi?.children?.flatMap {
+ it.safeAs<KtInitializerList>()?.initializersAsText().orEmpty()
+ }.orEmpty()
+
+ private fun KtInitializerList.initializersAsText() =
+ initializers.firstIsInstanceOrNull<KtCallElement>()
+ ?.getValueArgumentsInParentheses()
+ ?.flatMap { it.childrenAsText() }
+ .orEmpty()
+
+ private fun ValueArgument.childrenAsText() = this.safeAs<KtValueArgument>()?.children?.map { it.text }.orEmpty()
+
+ private data class InheritanceLevel(val level: Int, val superclass: DRI?, val interfaces: List<DRI>)
+
+ private data class ClassInfo(val inheritance: List<InheritanceLevel>, val docs: SourceSetDependent<DocumentationNode>){
+ val supertypes: List<DriWithKind>
+ get() = inheritance.firstOrNull { it.level == 0 }?.let {
+ listOfNotNull(it.superclass?.let { DriWithKind(it, KotlinClassKindTypes.CLASS) }) + it.interfaces.map { DriWithKind(it, KotlinClassKindTypes.INTERFACE) }
+ }.orEmpty()
+
+ val allImplementedInterfaces: List<DRI>
+ get() = inheritance.flatMap { it.interfaces }.distinct()
+ }
+
+ private fun Visibility.toDokkaVisibility(): org.jetbrains.dokka.model.Visibility = when (this) {
+ Visibilities.PUBLIC -> KotlinVisibility.Public
+ Visibilities.PROTECTED -> KotlinVisibility.Protected
+ Visibilities.INTERNAL -> KotlinVisibility.Internal
+ Visibilities.PRIVATE -> KotlinVisibility.Private
+ else -> KotlinVisibility.Public
+ }
+
+ private fun ConstantsEnumValue.fullEnumEntryName() =
+ "${this.enumClassId.relativeClassName.asString()}.${this.enumEntryName.identifier}"
+
+ private fun fallbackPackageName(): String =
+ "[${sourceSet.displayName} root]"// TODO: error-prone, find a better way to do it
+}
+
+private fun DRI.withPackageFallbackTo(fallbackPackage: String): DRI {
+ return if (packageName.isNullOrBlank()) {
+ copy(packageName = fallbackPackage)
+ } else {
+ this
+ }
+}
diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultDocumentableToPageTranslator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultDocumentableToPageTranslator.kt
new file mode 100644
index 00000000..04251947
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultDocumentableToPageTranslator.kt
@@ -0,0 +1,17 @@
+package org.jetbrains.dokka.base.translators.documentables
+
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.pages.ModulePageNode
+import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator
+import org.jetbrains.dokka.utilities.DokkaLogger
+
+class DefaultDocumentableToPageTranslator(
+ private val commentsToContentConverter: CommentsToContentConverter,
+ private val signatureProvider: SignatureProvider,
+ private val logger: DokkaLogger
+) : DocumentableToPageTranslator {
+ override fun invoke(module: DModule): ModulePageNode =
+ DefaultPageCreator(commentsToContentConverter, signatureProvider, logger).pageForModule(module)
+} \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
new file mode 100644
index 00000000..02f4b54e
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
@@ -0,0 +1,525 @@
+package org.jetbrains.dokka.base.translators.documentables
+
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.documentables.CallableExtensions
+import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo
+import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder.DocumentableContentBuilder
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.model.properties.WithExtraProperties
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.utils.addToStdlib.safeAs
+import kotlin.reflect.KClass
+import kotlin.reflect.full.isSubclassOf
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+
+private typealias GroupedTags = Map<KClass<out TagWrapper>, List<Pair<DokkaSourceSet?, TagWrapper>>>
+
+private val specialTags: Set<KClass<out TagWrapper>> =
+ setOf(Property::class, Description::class, Constructor::class, Receiver::class, Param::class, See::class)
+
+open class DefaultPageCreator(
+ commentsToContentConverter: CommentsToContentConverter,
+ signatureProvider: SignatureProvider,
+ val logger: DokkaLogger
+) {
+ protected open val contentBuilder = PageContentBuilder(commentsToContentConverter, signatureProvider, logger)
+
+ open fun pageForModule(m: DModule) =
+ ModulePageNode(m.name.ifEmpty { "<root>" }, contentForModule(m), m, m.packages.map(::pageForPackage))
+
+ open fun pageForPackage(p: DPackage): PackagePageNode = PackagePageNode(
+ p.name, contentForPackage(p), setOf(p.dri), p,
+ p.classlikes.map(::pageForClasslike) +
+ p.functions.map(::pageForFunction)
+ )
+
+ open fun pageForEnumEntry(e: DEnumEntry): ClasslikePageNode =
+ ClasslikePageNode(
+ e.name, contentForEnumEntry(e), setOf(e.dri), e,
+ e.classlikes.map(::pageForClasslike) +
+ e.filteredFunctions.map(::pageForFunction)
+ )
+
+ open fun pageForClasslike(c: DClasslike): ClasslikePageNode {
+ val constructors = if (c is WithConstructors) c.constructors else emptyList()
+
+ return ClasslikePageNode(
+ c.name.orEmpty(), contentForClasslike(c), setOf(c.dri), c,
+ constructors.map(::pageForFunction) +
+ c.classlikes.map(::pageForClasslike) +
+ c.filteredFunctions.map(::pageForFunction) +
+ if (c is DEnum) c.entries.map(::pageForEnumEntry) else emptyList()
+ )
+ }
+
+ open fun pageForFunction(f: DFunction) = MemberPageNode(f.name, contentForFunction(f), setOf(f.dri), f)
+
+ open fun pageForTypeAlias(t: DTypeAlias) = MemberPageNode(t.name, contentForTypeAlias(t), setOf(t.dri), t)
+
+ private val WithScope.filteredFunctions: List<DFunction>
+ get() = functions.mapNotNull { function ->
+ function.takeIf {
+ it.sourceSets.any { sourceSet -> it.extra[InheritedFunction]?.isInherited(sourceSet) != true }
+ }
+ }
+
+ protected open fun contentForModule(m: DModule) = contentBuilder.contentFor(m) {
+ group(kind = ContentKind.Cover) {
+ cover(m.name)
+ if (contentForDescription(m).isNotEmpty()) {
+ sourceSetDependentHint(
+ m.dri,
+ m.sourceSets.toSet(),
+ kind = ContentKind.SourceSetDependentHint,
+ styles = setOf(TextStyle.UnderCoverText)
+ ) {
+ +contentForDescription(m)
+ }
+ }
+ }
+ +contentForComments(m)
+ block("Packages", 2, ContentKind.Packages, m.packages, m.sourceSets.toSet()) {
+ link(it.name, it.dri)
+ }
+// text("Index\n") TODO
+// text("Link to allpage here")
+ }
+
+ protected open fun contentForPackage(p: DPackage) = contentBuilder.contentFor(p) {
+ group(kind = ContentKind.Cover) {
+ cover("Package ${p.name}")
+ if (contentForDescription(p).isNotEmpty()) {
+ sourceSetDependentHint(
+ p.dri,
+ p.sourceSets.toSet(),
+ kind = ContentKind.SourceSetDependentHint,
+ styles = setOf(TextStyle.UnderCoverText)
+ ) {
+ +contentForDescription(p)
+ }
+ }
+ }
+ group(styles = setOf(ContentStyle.TabbedContent)) {
+ +contentForComments(p)
+ +contentForScope(p, p.dri, p.sourceSets)
+ }
+ }
+
+ protected open fun contentForScope(
+ s: WithScope,
+ dri: DRI,
+ sourceSets: Set<DokkaSourceSet>
+ ) = contentBuilder.contentFor(s as Documentable) {
+ val types = listOf(
+ s.classlikes,
+ (s as? DPackage)?.typealiases ?: emptyList()
+ ).flatten()
+ divergentBlock("Types", types, ContentKind.Classlikes, extra = mainExtra + SimpleAttr.header("Types"))
+ divergentBlock(
+ "Functions",
+ s.functions,
+ ContentKind.Functions,
+ extra = mainExtra + SimpleAttr.header("Functions")
+ )
+ block(
+ "Properties",
+ 2,
+ ContentKind.Properties,
+ s.properties,
+ sourceSets.toSet(),
+ needsAnchors = true,
+ extra = mainExtra + SimpleAttr.header("Properties")
+ ) {
+ link(it.name, it.dri, kind = ContentKind.Main)
+ sourceSetDependentHint(it.dri, it.sourceSets.toSet(), kind = ContentKind.SourceSetDependentHint) {
+ contentForBrief(it)
+ +buildSignature(it)
+ }
+ }
+ s.safeAs<WithExtraProperties<Documentable>>()?.let { it.extra[InheritorsInfo] }?.let { inheritors ->
+ val map = inheritors.value.filter { it.value.isNotEmpty() }
+ if (map.values.any()) {
+ header(2, "Inheritors") { }
+ +ContentTable(
+ listOf(contentBuilder.contentFor(mainDRI, mainSourcesetData){
+ text("Name")
+ }),
+ map.entries.flatMap { entry -> entry.value.map { Pair(entry.key, it) } }
+ .groupBy({ it.second }, { it.first }).map { (classlike, platforms) ->
+ buildGroup(setOf(dri), platforms.toSet(), ContentKind.Inheritors) {
+ link(
+ classlike.classNames?.substringBeforeLast(".") ?: classlike.toString()
+ .also { logger.warn("No class name found for DRI $classlike") }, classlike
+ )
+ }
+ },
+ DCI(setOf(dri), ContentKind.Inheritors),
+ sourceSets.toSet(),
+ style = emptySet(),
+ extra = mainExtra + SimpleAttr.header("Inheritors")
+ )
+ }
+ }
+ }
+
+ protected open fun contentForEnumEntry(e: DEnumEntry) = contentBuilder.contentFor(e) {
+ group(kind = ContentKind.Cover) {
+ cover(e.name)
+ sourceSetDependentHint(e.dri, e.sourceSets.toSet()) {
+ +contentForDescription(e)
+ +buildSignature(e)
+ }
+ }
+ group(styles = setOf(ContentStyle.TabbedContent)) {
+ +contentForComments(e)
+ +contentForScope(e, e.dri, e.sourceSets)
+ }
+ }
+
+ protected open fun contentForClasslike(c: DClasslike) = contentBuilder.contentFor(c) {
+ @Suppress("UNCHECKED_CAST")
+ val extensions = (c as WithExtraProperties<DClasslike>)
+ .extra[CallableExtensions]?.extensions
+ ?.filterIsInstance<Documentable>().orEmpty()
+ // Extensions are added to sourceSets since they can be placed outside the sourceSets from classlike
+ // Example would be an Interface in common and extension function in jvm
+ group(kind = ContentKind.Cover, sourceSets = mainSourcesetData + extensions.sourceSets) {
+ cover(c.name.orEmpty())
+ sourceSetDependentHint(c.dri, c.sourceSets) {
+ +contentForDescription(c)
+ +buildSignature(c)
+ }
+ }
+
+ group(styles = setOf(ContentStyle.TabbedContent), sourceSets = mainSourcesetData + extensions.sourceSets) {
+ +contentForComments(c)
+ if (c is WithConstructors) {
+ block(
+ "Constructors",
+ 2,
+ ContentKind.Constructors,
+ c.constructors.filter { it.extra[PrimaryConstructorExtra] == null || it.documentation.isNotEmpty() },
+ c.sourceSets,
+ extra = PropertyContainer.empty<ContentNode>() + SimpleAttr.header("Constructors")
+ ) {
+ link(it.name, it.dri, kind = ContentKind.Main)
+ sourceSetDependentHint(
+ it.dri,
+ it.sourceSets.toSet(),
+ kind = ContentKind.SourceSetDependentHint,
+ styles = emptySet()
+ ) {
+ contentForBrief(it)
+ +buildSignature(it)
+ }
+ }
+ }
+ if (c is DEnum) {
+ block(
+ "Entries",
+ 2,
+ ContentKind.Classlikes,
+ c.entries,
+ c.sourceSets.toSet(),
+ needsSorting = false,
+ extra = mainExtra + SimpleAttr.header("Entries"),
+ styles = emptySet()
+ ) {
+ link(it.name, it.dri)
+ sourceSetDependentHint(it.dri, it.sourceSets.toSet(), kind = ContentKind.SourceSetDependentHint) {
+ contentForBrief(it)
+ +buildSignature(it)
+ }
+ }
+ }
+ +contentForScope(c, c.dri, c.sourceSets)
+
+ divergentBlock("Extensions", extensions, ContentKind.Extensions, extra = mainExtra + SimpleAttr.header("Extensions"))
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private inline fun <reified T : TagWrapper> GroupedTags.withTypeUnnamed(): SourceSetDependent<T> =
+ (this[T::class] as List<Pair<DokkaSourceSet, T>>?)?.toMap().orEmpty()
+
+ @Suppress("UNCHECKED_CAST")
+ private inline fun <reified T : NamedTagWrapper> GroupedTags.withTypeNamed(): Map<String, SourceSetDependent<T>> =
+ (this[T::class] as List<Pair<DokkaSourceSet, T>>?)
+ ?.groupBy { it.second.name }
+ ?.mapValues { (_, v) -> v.toMap() }
+ ?.toSortedMap(String.CASE_INSENSITIVE_ORDER)
+ .orEmpty()
+
+ private inline fun <reified T : TagWrapper> GroupedTags.isNotEmptyForTag(): Boolean =
+ this[T::class]?.isNotEmpty() ?: false
+
+ protected open fun contentForDescription(
+ d: Documentable
+ ): List<ContentNode> {
+ val tags: GroupedTags = d.documentation.flatMap { (pd, doc) ->
+ doc.children.asSequence().map { pd to it }.toList()
+ }.groupBy { it.second::class }
+
+ val platforms = d.sourceSets.toSet()
+
+ return contentBuilder.contentFor(d, styles = setOf(TextStyle.Block)) {
+ val description = tags.withTypeUnnamed<Description>()
+ if (description.any { it.value.root.children.isNotEmpty() }) {
+ platforms.forEach { platform ->
+ description[platform]?.also {
+ group(sourceSets = setOf(platform)) {
+ comment(it.root)
+ }
+ }
+ }
+ }
+
+ val unnamedTags: List<SourceSetDependent<TagWrapper>> =
+ tags.filterNot { (k, _) -> k.isSubclassOf(NamedTagWrapper::class) || k in specialTags }
+ .map { (_, v) -> v.mapNotNull { (k, v) -> k?.let { it to v } }.toMap() }
+ if (unnamedTags.isNotEmpty()) {
+ platforms.forEach { platform ->
+ unnamedTags.forEach { pdTag ->
+ pdTag[platform]?.also { tag ->
+ group(sourceSets = setOf(platform)) {
+ header(4, tag.toHeaderString())
+ comment(tag.root)
+ }
+ }
+ }
+ }
+ }
+
+ contentForSinceKotlin(d)
+ }.children
+ }
+
+ private fun Documentable.getPossibleFallbackSourcesets(sourceSet: DokkaSourceSet) =
+ this.sourceSets.filter { it.sourceSetID in sourceSet.dependentSourceSets }
+
+ private fun <V> Map<DokkaSourceSet, V>.fallback(sourceSets: List<DokkaSourceSet>): V? =
+ sourceSets.firstOrNull { it in this.keys }.let { this[it] }
+
+ protected open fun contentForComments(
+ d: Documentable
+ ): List<ContentNode> {
+ val tags: GroupedTags = d.documentation.flatMap { (pd, doc) ->
+ doc.children.asSequence().map { pd to it }.toList()
+ }.groupBy { it.second::class }
+
+ val platforms = d.sourceSets
+
+ fun DocumentableContentBuilder.contentForParams() {
+ if (tags.isNotEmptyForTag<Param>()) {
+ header(2, "Parameters", kind = ContentKind.Parameters)
+ group(
+ extra = mainExtra + SimpleAttr.header("Parameters"),
+ styles = setOf(ContentStyle.WithExtraAttributes)
+ ) {
+ sourceSetDependentHint(sourceSets = platforms.toSet(), kind = ContentKind.SourceSetDependentHint) {
+ val receiver = tags.withTypeUnnamed<Receiver>()
+ val params = tags.withTypeNamed<Param>()
+ table(kind = ContentKind.Parameters) {
+ platforms.flatMap { platform ->
+ val possibleFallbacks = d.getPossibleFallbackSourcesets(platform)
+ val receiverRow = (receiver[platform] ?: receiver.fallback(possibleFallbacks))?.let {
+ buildGroup(sourceSets = setOf(platform), kind = ContentKind.Parameters) {
+ text("<receiver>", styles = mainStyles + ContentStyle.RowTitle)
+ comment(it.root)
+ }
+ }
+
+ val paramRows = params.mapNotNull { (_, param) ->
+ (param[platform] ?: param.fallback(possibleFallbacks))?.let {
+ buildGroup(sourceSets = setOf(platform), kind = ContentKind.Parameters) {
+ text(
+ it.name,
+ kind = ContentKind.Parameters,
+ styles = mainStyles + ContentStyle.RowTitle
+ )
+ comment(it.root)
+ }
+ }
+ }
+
+ listOfNotNull(receiverRow) + paramRows
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun DocumentableContentBuilder.contentForSeeAlso() {
+ if (tags.isNotEmptyForTag<See>()) {
+ header(2, "See also", kind = ContentKind.Comment)
+ group(
+ extra = mainExtra + SimpleAttr.header("See also"),
+ styles = setOf(ContentStyle.WithExtraAttributes)
+ ) {
+ sourceSetDependentHint(sourceSets = platforms.toSet(), kind = ContentKind.SourceSetDependentHint) {
+ val seeAlsoTags = tags.withTypeNamed<See>()
+ table(kind = ContentKind.Sample) {
+ platforms.flatMap { platform ->
+ val possibleFallbacks = d.getPossibleFallbackSourcesets(platform)
+ seeAlsoTags.mapNotNull { (_, see) ->
+ (see[platform] ?: see.fallback(possibleFallbacks))?.let {
+ buildGroup(
+ sourceSets = setOf(platform),
+ kind = ContentKind.Comment,
+ styles = mainStyles + ContentStyle.RowTitle
+ ) {
+ if (it.address != null) link(
+ it.name,
+ it.address!!,
+ kind = ContentKind.Comment
+ )
+ else text(it.name, kind = ContentKind.Comment)
+ comment(it.root)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun DocumentableContentBuilder.contentForSamples() {
+ val samples = tags.withTypeNamed<Sample>()
+ if (samples.isNotEmpty()) {
+ header(2, "Samples", kind = ContentKind.Sample)
+ group(
+ extra = mainExtra + SimpleAttr.header("Samples"),
+ styles = emptySet()
+ ) {
+ sourceSetDependentHint(sourceSets = platforms.toSet(), kind = ContentKind.SourceSetDependentHint) {
+ platforms.map { platformData ->
+ val content = samples.filter { it.value.isEmpty() || platformData in it.value }
+ group(
+ sourceSets = setOf(platformData),
+ kind = ContentKind.Sample,
+ styles = setOf(TextStyle.Monospace, ContentStyle.RunnableSample)
+ ) {
+ content.forEach {
+ text(it.key)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return contentBuilder.contentFor(d) {
+ if (tags.isNotEmpty()) {
+ contentForSamples()
+ contentForSeeAlso()
+ contentForParams()
+ }
+ }.children
+ }
+
+ protected open fun DocumentableContentBuilder.contentForBrief(documentable: Documentable) {
+ documentable.sourceSets.forEach { sourceSet ->
+ documentable.documentation[sourceSet]?.children?.firstOrNull()?.root?.let {
+ group(sourceSets = setOf(sourceSet), kind = ContentKind.BriefComment) {
+ comment(it)
+ }
+ }
+ }
+ }
+
+ protected open fun DocumentableContentBuilder.contentForSinceKotlin(documentable: Documentable) {
+ documentable.documentation.mapValues {
+ it.value.children.find { it is CustomTagWrapper && it.name == "Since Kotlin" } as CustomTagWrapper?
+ }.run {
+ documentable.sourceSets.forEach { sourceSet ->
+ this[sourceSet]?.also { tag ->
+ group(sourceSets = setOf(sourceSet), kind = ContentKind.Comment, styles = setOf(TextStyle.Block)) {
+ header(4, tag.name)
+ comment(tag.root)
+ }
+ }
+ }
+ }
+ }
+
+ protected open fun contentForFunction(f: DFunction) = contentForMember(f)
+ protected open fun contentForTypeAlias(t: DTypeAlias) = contentForMember(t)
+ protected open fun contentForMember(d: Documentable) = contentBuilder.contentFor(d) {
+ group(kind = ContentKind.Cover) {
+ cover(d.name.orEmpty())
+ }
+ divergentGroup(ContentDivergentGroup.GroupID("member")) {
+ instance(setOf(d.dri), d.sourceSets.toSet()) {
+ before {
+ +contentForDescription(d)
+ +contentForComments(d)
+ }
+ divergent(kind = ContentKind.Symbol) {
+ +buildSignature(d)
+ }
+ }
+ }
+ }
+
+ protected open fun DocumentableContentBuilder.divergentBlock(
+ name: String,
+ collection: Collection<Documentable>,
+ kind: ContentKind,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) {
+ if (collection.any()) {
+ header(2, name, kind = kind)
+ table(kind, extra = extra, styles = emptySet()) {
+ collection
+ .groupBy { it.name }
+ // This hacks displaying actual typealias signatures along classlike ones
+ .mapValues { if (it.value.any { it is DClasslike }) it.value.filter { it !is DTypeAlias } else it.value }
+ .toSortedMap(compareBy(nullsLast(String.CASE_INSENSITIVE_ORDER)){it})
+ .map { (elementName, elements) -> // This groupBy should probably use LocationProvider
+ buildGroup(
+ dri = elements.map { it.dri }.toSet(),
+ sourceSets = elements.flatMap { it.sourceSets }.toSet(),
+ kind = kind,
+ styles = emptySet()
+ ) {
+ link(elementName.orEmpty(), elements.first().dri, kind = kind)
+ divergentGroup(
+ ContentDivergentGroup.GroupID(name),
+ elements.map { it.dri }.toSet(),
+ kind = kind
+ ) {
+ elements.map {
+ instance(setOf(it.dri), it.sourceSets.toSet()) {
+ before {
+ contentForBrief(it)
+ contentForSinceKotlin(it)
+ }
+ divergent {
+ group {
+ +buildSignature(it)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ protected open fun TagWrapper.toHeaderString() = this.javaClass.toGenericString().split('.').last()
+
+ private val List<Documentable>.sourceSets: Set<DokkaSourceSet>
+ get() = flatMap { it.sourceSets }.toSet()
+}
diff --git a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt
new file mode 100644
index 00000000..b7927076
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt
@@ -0,0 +1,474 @@
+package org.jetbrains.dokka.base.translators.documentables
+
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.SourceSetDependent
+import org.jetbrains.dokka.model.doc.DocTag
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.utilities.DokkaLogger
+
+@DslMarker
+annotation class ContentBuilderMarker
+
+open class PageContentBuilder(
+ val commentsConverter: CommentsToContentConverter,
+ val signatureProvider: SignatureProvider,
+ val logger: DokkaLogger
+) {
+ fun contentFor(
+ dri: DRI,
+ sourceSets: Set<DokkaSourceSet>,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = emptySet(),
+ extra: PropertyContainer<ContentNode> = PropertyContainer.empty(),
+ block: DocumentableContentBuilder.() -> Unit
+ ): ContentGroup =
+ DocumentableContentBuilder(setOf(dri), sourceSets, styles, extra)
+ .apply(block)
+ .build(sourceSets, kind, styles, extra)
+
+ fun contentFor(
+ dri: Set<DRI>,
+ sourceSets: Set<DokkaSourceSet>,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = emptySet(),
+ extra: PropertyContainer<ContentNode> = PropertyContainer.empty(),
+ block: DocumentableContentBuilder.() -> Unit
+ ): ContentGroup =
+ DocumentableContentBuilder(dri, sourceSets, styles, extra)
+ .apply(block)
+ .build(sourceSets, kind, styles, extra)
+
+ fun contentFor(
+ d: Documentable,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = emptySet(),
+ extra: PropertyContainer<ContentNode> = PropertyContainer.empty(),
+ sourceSets: Set<DokkaSourceSet> = d.sourceSets.toSet(),
+ block: DocumentableContentBuilder.() -> Unit = {}
+ ): ContentGroup =
+ DocumentableContentBuilder(setOf(d.dri), sourceSets, styles, extra)
+ .apply(block)
+ .build(sourceSets, kind, styles, extra)
+
+ @ContentBuilderMarker
+ open inner class DocumentableContentBuilder(
+ val mainDRI: Set<DRI>,
+ val mainSourcesetData: Set<DokkaSourceSet>,
+ val mainStyles: Set<Style>,
+ val mainExtra: PropertyContainer<ContentNode>
+ ) {
+ protected val contents = mutableListOf<ContentNode>()
+
+ fun build(
+ sourceSets: Set<DokkaSourceSet>,
+ kind: Kind,
+ styles: Set<Style>,
+ extra: PropertyContainer<ContentNode>
+ ) = ContentGroup(
+ contents.toList(),
+ DCI(mainDRI, kind),
+ sourceSets,
+ styles,
+ extra
+ )
+
+ operator fun ContentNode.unaryPlus() {
+ contents += this
+ }
+
+ operator fun Collection<ContentNode>.unaryPlus() {
+ contents += this
+ }
+
+ private val defaultHeaders
+ get() = listOf(
+ contentFor(mainDRI, mainSourcesetData){
+ text("Name")
+ },
+ contentFor(mainDRI, mainSourcesetData){
+ text("Summary")
+ }
+ )
+
+ fun header(
+ level: Int,
+ text: String,
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit = {}
+ ) {
+ contents += ContentHeader(
+ level,
+ contentFor(
+ mainDRI,
+ sourceSets,
+ kind,
+ styles,
+ extra + SimpleAttr("anchor", text.replace("\\s".toRegex(), "").toLowerCase())
+ ) {
+ text(text, kind = kind)
+ block()
+ }
+ )
+ }
+
+ fun cover(
+ text: String,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles + TextStyle.Cover,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit = {}
+ ) {
+ header(1, text, sourceSets = sourceSets, styles = styles, extra = extra, block = block)
+ }
+
+ fun text(
+ text: String,
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) {
+ contents += createText(text, kind, sourceSets, styles, extra)
+ }
+
+ fun buildSignature(d: Documentable) = signatureProvider.signature(d)
+
+ fun table(
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ operation: DocumentableContentBuilder.() -> List<ContentGroup>
+ ) {
+ contents += ContentTable(
+ defaultHeaders,
+ operation(),
+ DCI(mainDRI, kind),
+ sourceSets, styles, extra
+ )
+ }
+
+ fun <T : Documentable> block(
+ name: String,
+ level: Int,
+ kind: Kind = ContentKind.Main,
+ elements: Iterable<T>,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ renderWhenEmpty: Boolean = false,
+ needsSorting: Boolean = true,
+ headers: List<ContentGroup>? = null,
+ needsAnchors: Boolean = false,
+ operation: DocumentableContentBuilder.(T) -> Unit
+ ) {
+ if (renderWhenEmpty || elements.any()) {
+ header(level, name, kind = kind) { }
+ contents += ContentTable(
+ headers ?: defaultHeaders,
+ elements
+ .let {
+ if (needsSorting)
+ it.sortedWith(compareBy(nullsLast(String.CASE_INSENSITIVE_ORDER)) { it.name })
+ else it
+ }
+ .map {
+ val newExtra = if (needsAnchors) extra + SymbolAnchorHint else extra
+ buildGroup(setOf(it.dri), it.sourceSets.toSet(), kind, styles, newExtra) {
+ operation(it)
+ }
+ },
+ DCI(mainDRI, kind),
+ sourceSets, styles, extra
+ )
+ }
+ }
+
+ fun <T> list(
+ elements: List<T>,
+ prefix: String = "",
+ suffix: String = "",
+ separator: String = ", ",
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData, // TODO: children should be aware of this platform data
+ operation: DocumentableContentBuilder.(T) -> Unit
+ ) {
+ if (elements.isNotEmpty()) {
+ if (prefix.isNotEmpty()) text(prefix, sourceSets = sourceSets)
+ elements.dropLast(1).forEach {
+ operation(it)
+ text(separator, sourceSets = sourceSets)
+ }
+ operation(elements.last())
+ if (suffix.isNotEmpty()) text(suffix, sourceSets = sourceSets)
+ }
+ }
+
+ fun link(
+ text: String,
+ address: DRI,
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) {
+ contents += linkNode(text, address, kind, sourceSets, styles, extra)
+ }
+
+ fun linkNode(
+ text: String,
+ address: DRI,
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) = ContentDRILink(
+ listOf(createText(text, kind, sourceSets, styles, extra)),
+ address,
+ DCI(mainDRI, kind),
+ sourceSets
+ )
+
+ fun link(
+ text: String,
+ address: String,
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) {
+ contents += ContentResolvedLink(
+ children = listOf(createText(text, kind, sourceSets, styles, extra)),
+ address = address,
+ extra = PropertyContainer.empty(),
+ dci = DCI(mainDRI, kind),
+ sourceSets = sourceSets,
+ style = emptySet()
+ )
+ }
+
+ fun link(
+ address: DRI,
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ contents += ContentDRILink(
+ contentFor(mainDRI, sourceSets, kind, styles, extra, block).children,
+ address,
+ DCI(mainDRI, kind),
+ sourceSets
+ )
+ }
+
+ fun comment(
+ docTag: DocTag,
+ kind: Kind = ContentKind.Comment,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) {
+ val content = commentsConverter.buildContent(
+ docTag,
+ DCI(mainDRI, kind),
+ sourceSets
+ )
+ contents += ContentGroup(content, DCI(mainDRI, kind), sourceSets, styles, extra)
+ }
+
+ fun group(
+ dri: Set<DRI> = mainDRI,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ contents += buildGroup(dri, sourceSets, kind, styles, extra, block)
+ }
+
+ fun divergentGroup(
+ groupID: ContentDivergentGroup.GroupID,
+ dri: Set<DRI> = mainDRI,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ implicitlySourceSetHinted: Boolean = true,
+ block: DivergentBuilder.() -> Unit
+ ) {
+ contents +=
+ DivergentBuilder(dri, kind, styles, extra)
+ .apply(block)
+ .build(groupID = groupID, implicitlySourceSetHinted = implicitlySourceSetHinted)
+ }
+
+ fun buildGroup(
+ dri: Set<DRI> = mainDRI,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ): ContentGroup = contentFor(dri, sourceSets, kind, styles, extra, block)
+
+ fun sourceSetDependentHint(
+ dri: Set<DRI> = mainDRI,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ contents += PlatformHintedContent(
+ buildGroup(dri, sourceSets, kind, styles, extra, block),
+ sourceSets
+ )
+ }
+
+ fun sourceSetDependentHint(
+ dri: DRI,
+ sourcesetData: Set<DokkaSourceSet> = mainSourcesetData,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ contents += PlatformHintedContent(
+ buildGroup(setOf(dri), sourcesetData, kind, styles, extra, block),
+ sourcesetData
+ )
+ }
+
+ protected fun createText(
+ text: String,
+ kind: Kind,
+ sourceSets: Set<DokkaSourceSet>,
+ styles: Set<Style>,
+ extra: PropertyContainer<ContentNode>
+ ) =
+ ContentText(text, DCI(mainDRI, kind), sourceSets, styles, extra)
+
+ fun <T> sourceSetDependentText(
+ value: SourceSetDependent<T>,
+ sourceSets: Set<DokkaSourceSet> = value.keys,
+ transform: (T) -> String
+ ) = value.entries.filter { it.key in sourceSets }.mapNotNull { (p, v) ->
+ transform(v).takeIf { it.isNotBlank() }?.let { it to p }
+ }.groupBy({ it.first }) { it.second }.forEach {
+ text(it.key, sourceSets = it.value.toSet())
+ }
+ }
+
+ @ContentBuilderMarker
+ open inner class DivergentBuilder(
+ private val mainDRI: Set<DRI>,
+ private val mainKind: Kind,
+ private val mainStyles: Set<Style>,
+ private val mainExtra: PropertyContainer<ContentNode>
+ ) {
+ private val instances: MutableList<ContentDivergentInstance> = mutableListOf()
+ fun instance(
+ dri: Set<DRI>,
+ sourceSets: Set<DokkaSourceSet>, // Having correct sourcesetData is crucial here, that's why there's no default
+ kind: Kind = mainKind,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DivergentInstanceBuilder.() -> Unit
+ ) {
+ instances += DivergentInstanceBuilder(dri, sourceSets, styles, extra)
+ .apply(block)
+ .build(kind)
+ }
+
+ fun build(
+ groupID: ContentDivergentGroup.GroupID,
+ implicitlySourceSetHinted: Boolean,
+ kind: Kind = mainKind,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) = ContentDivergentGroup(
+ instances.toList(),
+ DCI(mainDRI, kind),
+ styles,
+ extra,
+ groupID,
+ implicitlySourceSetHinted
+ )
+ }
+
+ @ContentBuilderMarker
+ open inner class DivergentInstanceBuilder(
+ private val mainDRI: Set<DRI>,
+ private val mainSourceSets: Set<DokkaSourceSet>,
+ private val mainStyles: Set<Style>,
+ private val mainExtra: PropertyContainer<ContentNode>
+ ) {
+ private var before: ContentNode? = null
+ private var divergent: ContentNode? = null
+ private var after: ContentNode? = null
+
+ fun before(
+ dri: Set<DRI> = mainDRI,
+ sourceSets: Set<DokkaSourceSet> = mainSourceSets,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ contentFor(dri, sourceSets, kind, styles, extra, block)
+ .takeIf { it.hasAnyContent() }
+ .also { before = it }
+ }
+
+ fun divergent(
+ dri: Set<DRI> = mainDRI,
+ sourceSets: Set<DokkaSourceSet> = mainSourceSets,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ divergent = contentFor(dri, sourceSets, kind, styles, extra, block)
+ }
+
+ fun after(
+ dri: Set<DRI> = mainDRI,
+ sourceSets: Set<DokkaSourceSet> = mainSourceSets,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ contentFor(dri, sourceSets, kind, styles, extra, block)
+ .takeIf { it.hasAnyContent() }
+ .also { after = it }
+ }
+
+
+ fun build(
+ kind: Kind,
+ sourceSets: Set<DokkaSourceSet> = mainSourceSets,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) =
+ ContentDivergentInstance(
+ before,
+ divergent ?: throw IllegalStateException("Divergent block needs divergent part"),
+ after,
+ DCI(mainDRI, kind),
+ sourceSets,
+ styles,
+ extra
+ )
+ }
+} \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt
new file mode 100644
index 00000000..6f980383
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt
@@ -0,0 +1,480 @@
+package org.jetbrains.dokka.base.translators.psi
+
+import com.intellij.lang.jvm.JvmModifier
+import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute
+import com.intellij.lang.jvm.types.JvmReferenceType
+import com.intellij.openapi.vfs.VirtualFileManager
+import com.intellij.psi.*
+import com.intellij.psi.impl.source.PsiClassReferenceType
+import com.intellij.psi.impl.source.PsiImmediateClassType
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.analysis.KotlinAnalysis
+import org.jetbrains.dokka.analysis.PsiDocumentableSource
+import org.jetbrains.dokka.analysis.from
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.DriWithKind
+import org.jetbrains.dokka.links.nextTarget
+import org.jetbrains.dokka.links.withClass
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.doc.DocumentationLink
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import org.jetbrains.dokka.model.doc.Param
+import org.jetbrains.dokka.model.doc.Text
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.transformers.sources.SourceToDocumentableTranslator
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation
+import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
+import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
+import org.jetbrains.kotlin.descriptors.Visibilities
+import org.jetbrains.kotlin.load.java.JvmAbi
+import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName
+import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
+import org.jetbrains.kotlin.resolve.DescriptorUtils
+import org.jetbrains.kotlin.utils.addToStdlib.safeAs
+import java.io.File
+
+class DefaultPsiToDocumentableTranslator(
+ private val kotlinAnalysis: KotlinAnalysis
+) : SourceToDocumentableTranslator {
+
+ override fun invoke(sourceSet: DokkaSourceSet, context: DokkaContext): DModule {
+
+ fun isFileInSourceRoots(file: File) : Boolean {
+ return sourceSet.sourceRoots.any { root -> file.path.startsWith(File(root.path).absolutePath) }
+ }
+
+ val (environment, _) = kotlinAnalysis[sourceSet]
+
+ val sourceRoots = environment.configuration.get(CLIConfigurationKeys.CONTENT_ROOTS)
+ ?.filterIsInstance<JavaSourceRoot>()
+ ?.mapNotNull { it.file.takeIf(::isFileInSourceRoots) }
+ ?: listOf()
+ val localFileSystem = VirtualFileManager.getInstance().getFileSystem("file")
+
+ val psiFiles = sourceRoots.map { sourceRoot ->
+ sourceRoot.absoluteFile.walkTopDown().mapNotNull {
+ localFileSystem.findFileByPath(it.path)?.let { vFile ->
+ PsiManager.getInstance(environment.project).findFile(vFile) as? PsiJavaFile
+ }
+ }.toList()
+ }.flatten()
+
+ val docParser =
+ DokkaPsiParser(
+ sourceSet,
+ context.logger
+ )
+ return DModule(
+ sourceSet.moduleDisplayName,
+ psiFiles.mapNotNull { it.safeAs<PsiJavaFile>() }.groupBy { it.packageName }.map { (packageName, psiFiles) ->
+ val dri = DRI(packageName = packageName)
+ DPackage(
+ dri,
+ emptyList(),
+ emptyList(),
+ psiFiles.flatMap { psiFile ->
+ psiFile.classes.map { docParser.parseClasslike(it, dri) }
+ },
+ emptyList(),
+ emptyMap(),
+ null,
+ setOf(sourceSet)
+ )
+ },
+ emptyMap(),
+ null,
+ setOf(sourceSet)
+ )
+ }
+
+ class DokkaPsiParser(
+ private val sourceSetData: DokkaSourceSet,
+ private val logger: DokkaLogger
+ ) {
+
+ private val javadocParser: JavaDocumentationParser = JavadocParser(logger)
+
+ private val cachedBounds = hashMapOf<String, Bound>()
+
+ private fun PsiModifierListOwner.getVisibility() = modifierList?.children?.toList()?.let { ml ->
+ when {
+ ml.any { it.text == PsiKeyword.PUBLIC } -> JavaVisibility.Public
+ ml.any { it.text == PsiKeyword.PROTECTED } -> JavaVisibility.Protected
+ ml.any { it.text == PsiKeyword.PRIVATE } -> JavaVisibility.Private
+ else -> JavaVisibility.Default
+ }
+ } ?: JavaVisibility.Default
+
+ private val PsiMethod.hash: Int
+ get() = "$returnType $name$parameterList".hashCode()
+
+ private val PsiClassType.shouldBeIgnored: Boolean
+ get() = isClass("java.lang.Enum") || isClass("java.lang.Object")
+
+ private fun PsiClassType.isClass(qName: String): Boolean {
+ val shortName = qName.substringAfterLast('.')
+ if (className == shortName) {
+ val psiClass = resolve()
+ return psiClass?.qualifiedName == qName
+ }
+ return false
+ }
+
+ private fun <T> T.toSourceSetDependent() = mapOf(sourceSetData to this)
+
+ fun parseClasslike(psi: PsiClass, parent: DRI): DClasslike = with(psi) {
+ val dri = parent.withClass(name.toString())
+ val inheritanceTree = mutableListOf<AncestorLevel>()
+ val superMethodsKeys = hashSetOf<Int>()
+ val superMethods = mutableListOf<Pair<PsiMethod, DRI>>()
+ methods.forEach { superMethodsKeys.add(it.hash) }
+ fun parseSupertypes(superTypes: Array<PsiClassType>, level: Int = 0) {
+ if(superTypes.isEmpty()) return
+ val parsedClasses = superTypes.filter { !it.shouldBeIgnored }.mapNotNull {
+ it.resolve()?.let {
+ when {
+ it.isInterface -> DRI.from(it) to JavaClassKindTypes.INTERFACE
+ else -> DRI.from(it) to JavaClassKindTypes.CLASS
+ }
+ }
+ }
+ val (classes, interfaces) = parsedClasses.partition { it.second == JavaClassKindTypes.CLASS }
+ inheritanceTree.add(AncestorLevel(level, classes.firstOrNull()?.first, interfaces.map { it.first }))
+
+ superTypes.forEach { type ->
+ (type as? PsiClassType)?.takeUnless { type.shouldBeIgnored }?.resolve()?.let {
+ val definedAt = DRI.from(it)
+ it.methods.forEach { method ->
+ val hash = method.hash
+ if (!method.isConstructor && !superMethodsKeys.contains(hash) &&
+ method.getVisibility() != Visibilities.PRIVATE
+ ) {
+ superMethodsKeys.add(hash)
+ superMethods.add(Pair(method, definedAt))
+ }
+ }
+ parseSupertypes(it.superTypes, level + 1)
+ }
+ }
+ }
+ parseSupertypes(superTypes)
+ val (regularFunctions, accessors) = splitFunctionsAndAccessors()
+ val documentation = javadocParser.parseDocumentation(this).toSourceSetDependent()
+ val allFunctions = regularFunctions.mapNotNull { if (!it.isConstructor) parseFunction(it) else null } +
+ superMethods.map { parseFunction(it.first, inheritedFrom = it.second) }
+ val source = PsiDocumentableSource(this).toSourceSetDependent()
+ val classlikes = innerClasses.map { parseClasslike(it, dri) }
+ val visibility = getVisibility().toSourceSetDependent()
+ val ancestors = inheritanceTree.filter { it.level == 0 }.flatMap {
+ listOfNotNull(it.superclass?.let {
+ DriWithKind(
+ dri = it,
+ kind = JavaClassKindTypes.CLASS
+ )
+ }) + it.interfaces.map { DriWithKind(dri = it, kind = JavaClassKindTypes.INTERFACE) }
+ }.toSourceSetDependent()
+ val modifiers = getModifier().toSourceSetDependent()
+ val implementedInterfacesExtra = ImplementedInterfaces(inheritanceTree.flatMap { it.interfaces }.distinct().toSourceSetDependent())
+ return when {
+ isAnnotationType ->
+ DAnnotation(
+ name.orEmpty(),
+ dri,
+ documentation,
+ null,
+ source,
+ allFunctions,
+ fields.mapNotNull { parseField(it, accessors[it].orEmpty()) },
+ classlikes,
+ visibility,
+ null,
+ constructors.map { parseFunction(it, true) },
+ mapTypeParameters(dri),
+ setOf(sourceSetData),
+ PropertyContainer.withAll(implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent()
+ .toAnnotations())
+ )
+ isEnum -> DEnum(
+ dri,
+ name.orEmpty(),
+ fields.filterIsInstance<PsiEnumConstant>().map { entry ->
+ DEnumEntry(
+ dri.withClass("${entry.name}"),
+ entry.name,
+ javadocParser.parseDocumentation(entry).toSourceSetDependent(),
+ null,
+ emptyList(),
+ emptyList(),
+ emptyList(),
+ setOf(sourceSetData),
+ PropertyContainer.withAll(implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent()
+ .toAnnotations())
+ )
+ },
+ documentation,
+ null,
+ source,
+ allFunctions,
+ fields.filter { it !is PsiEnumConstant }.map { parseField(it, accessors[it].orEmpty()) },
+ classlikes,
+ visibility,
+ null,
+ constructors.map { parseFunction(it, true) },
+ ancestors,
+ setOf(sourceSetData),
+ PropertyContainer.withAll(implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent()
+ .toAnnotations())
+ )
+ isInterface -> DInterface(
+ dri,
+ name.orEmpty(),
+ documentation,
+ null,
+ source,
+ allFunctions,
+ fields.mapNotNull { parseField(it, accessors[it].orEmpty()) },
+ classlikes,
+ visibility,
+ null,
+ mapTypeParameters(dri),
+ ancestors,
+ setOf(sourceSetData),
+ PropertyContainer.withAll(implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent()
+ .toAnnotations())
+ )
+ else -> DClass(
+ dri,
+ name.orEmpty(),
+ constructors.map { parseFunction(it, true) },
+ allFunctions,
+ fields.mapNotNull { parseField(it, accessors[it].orEmpty()) },
+ classlikes,
+ source,
+ visibility,
+ null,
+ mapTypeParameters(dri),
+ ancestors,
+ documentation,
+ null,
+ modifiers,
+ setOf(sourceSetData),
+ PropertyContainer.withAll(implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent()
+ .toAnnotations())
+ )
+ }
+ }
+
+ private fun parseFunction(
+ psi: PsiMethod,
+ isConstructor: Boolean = false,
+ inheritedFrom: DRI? = null
+ ): DFunction {
+ val dri = DRI.from(psi)
+ val docs = javadocParser.parseDocumentation(psi)
+ return DFunction(
+ dri,
+ if (isConstructor) "<init>" else psi.name,
+ isConstructor,
+ psi.parameterList.parameters.map { psiParameter ->
+ DParameter(
+ dri.copy(target = dri.target.nextTarget()),
+ psiParameter.name,
+ DocumentationNode(
+ listOfNotNull(docs.firstChildOfTypeOrNull<Param> {
+ it.firstChildOfTypeOrNull<DocumentationLink>()
+ ?.firstChildOfTypeOrNull<Text>()?.body == psiParameter.name
+ })).toSourceSetDependent(),
+ null,
+ getBound(psiParameter.type),
+ setOf(sourceSetData)
+ )
+ },
+ docs.toSourceSetDependent(),
+ null,
+ PsiDocumentableSource(psi).toSourceSetDependent(),
+ psi.getVisibility().toSourceSetDependent(),
+ psi.returnType?.let { getBound(type = it) } ?: Void,
+ psi.mapTypeParameters(dri),
+ null,
+ psi.getModifier().toSourceSetDependent(),
+ setOf(sourceSetData),
+ psi.additionalExtras().let {
+ PropertyContainer.withAll(
+ InheritedFunction(inheritedFrom.toSourceSetDependent()),
+ it.toSourceSetDependent().toAdditionalModifiers(),
+ (psi.annotations.toList().toListOfAnnotations() + it.toListOfAnnotations()).toSourceSetDependent()
+ .toAnnotations()
+ )
+ }
+ )
+ }
+
+ private fun PsiModifierListOwner.additionalExtras() = listOfNotNull(
+ ExtraModifiers.JavaOnlyModifiers.Static.takeIf { hasModifier(JvmModifier.STATIC) },
+ ExtraModifiers.JavaOnlyModifiers.Native.takeIf { hasModifier(JvmModifier.NATIVE) },
+ ExtraModifiers.JavaOnlyModifiers.Synchronized.takeIf { hasModifier(JvmModifier.SYNCHRONIZED) },
+ ExtraModifiers.JavaOnlyModifiers.StrictFP.takeIf { hasModifier(JvmModifier.STRICTFP) },
+ ExtraModifiers.JavaOnlyModifiers.Transient.takeIf { hasModifier(JvmModifier.TRANSIENT) },
+ ExtraModifiers.JavaOnlyModifiers.Volatile.takeIf { hasModifier(JvmModifier.VOLATILE) },
+ ExtraModifiers.JavaOnlyModifiers.Transitive.takeIf { hasModifier(JvmModifier.TRANSITIVE) }
+ ).toSet()
+
+ private fun Set<ExtraModifiers>.toListOfAnnotations() = map {
+ if (it !is ExtraModifiers.JavaOnlyModifiers.Static)
+ Annotations.Annotation(DRI("kotlin.jvm", it.name.toLowerCase().capitalize()), emptyMap())
+ else
+ Annotations.Annotation(DRI("kotlin.jvm", "JvmStatic"), emptyMap())
+ }
+
+ private fun getBound(type: PsiType): Bound =
+ cachedBounds.getOrPut(type.canonicalText) {
+ when (type) {
+ is PsiClassReferenceType -> {
+ val resolved: PsiClass = type.resolve()
+ ?: return UnresolvedBound(type.presentableText)
+ when {
+ resolved.qualifiedName == "java.lang.Object" -> JavaObject
+ resolved is PsiTypeParameter && resolved.owner != null ->
+ OtherParameter(
+ declarationDRI = DRI.from(resolved.owner!!),
+ name = resolved.name.orEmpty()
+ )
+ else ->
+ TypeConstructor(DRI.from(resolved), type.parameters.map { getProjection(it) })
+ }
+ }
+ is PsiArrayType -> TypeConstructor(
+ DRI("kotlin", "Array"),
+ listOf(getProjection(type.componentType))
+ )
+ is PsiPrimitiveType -> if (type.name == "void") Void else PrimitiveJavaType(type.name)
+ is PsiImmediateClassType -> JavaObject
+ else -> throw IllegalStateException("${type.presentableText} is not supported by PSI parser")
+ }
+ }
+
+ private fun getVariance(type: PsiWildcardType): Projection = when {
+ type.extendsBound != PsiType.NULL -> Variance(Variance.Kind.Out, getBound(type.extendsBound))
+ type.superBound != PsiType.NULL -> Variance(Variance.Kind.In, getBound(type.superBound))
+ else -> throw IllegalStateException("${type.presentableText} has incorrect bounds")
+ }
+
+ private fun getProjection(type: PsiType): Projection = when (type) {
+ is PsiEllipsisType -> Star
+ is PsiWildcardType -> getVariance(type)
+ else -> getBound(type)
+ }
+
+ private fun PsiModifierListOwner.getModifier() = when {
+ hasModifier(JvmModifier.ABSTRACT) -> JavaModifier.Abstract
+ hasModifier(JvmModifier.FINAL) -> JavaModifier.Final
+ else -> JavaModifier.Empty
+ }
+
+ private fun PsiTypeParameterListOwner.mapTypeParameters(dri: DRI): List<DTypeParameter> {
+ fun mapBounds(bounds: Array<JvmReferenceType>): List<Bound> =
+ if (bounds.isEmpty()) emptyList() else bounds.mapNotNull {
+ (it as? PsiClassType)?.let { classType -> Nullable(getBound(classType)) }
+ }
+ return typeParameters.map { type ->
+ DTypeParameter(
+ dri.copy(target = dri.target.nextTarget()),
+ type.name.orEmpty(),
+ javadocParser.parseDocumentation(type).toSourceSetDependent(),
+ null,
+ mapBounds(type.bounds),
+ setOf(sourceSetData)
+ )
+ }
+ }
+
+ private fun PsiMethod.getPropertyNameForFunction() =
+ getAnnotation(DescriptorUtils.JVM_NAME.asString())?.findAttributeValue("name")?.text
+ ?: when {
+ JvmAbi.isGetterName(name) -> propertyNameByGetMethodName(Name.identifier(name))?.asString()
+ JvmAbi.isSetterName(name) -> propertyNamesBySetMethodName(Name.identifier(name)).firstOrNull()
+ ?.asString()
+ else -> null
+ }
+
+ private fun PsiClass.splitFunctionsAndAccessors(): Pair<MutableList<PsiMethod>, MutableMap<PsiField, MutableList<PsiMethod>>> {
+ val fieldNames = fields.map { it.name to it }.toMap()
+ val accessors = mutableMapOf<PsiField, MutableList<PsiMethod>>()
+ val regularMethods = mutableListOf<PsiMethod>()
+ methods.forEach { method ->
+ val field = method.getPropertyNameForFunction()?.let { name -> fieldNames[name] }
+ if (field != null) {
+ accessors.getOrPut(field, ::mutableListOf).add(method)
+ } else {
+ regularMethods.add(method)
+ }
+ }
+ return regularMethods to accessors
+ }
+
+ private fun parseField(psi: PsiField, accessors: List<PsiMethod>): DProperty {
+ val dri = DRI.from(psi)
+ return DProperty(
+ dri,
+ psi.name,
+ javadocParser.parseDocumentation(psi).toSourceSetDependent(),
+ null,
+ PsiDocumentableSource(psi).toSourceSetDependent(),
+ psi.getVisibility().toSourceSetDependent(),
+ getBound(psi.type),
+ null,
+ accessors.firstOrNull { it.hasParameters() }?.let { parseFunction(it) },
+ accessors.firstOrNull { it.returnType == psi.type }?.let { parseFunction(it) },
+ psi.getModifier().toSourceSetDependent(),
+ setOf(sourceSetData),
+ emptyList(),
+ psi.additionalExtras().let {
+ PropertyContainer.withAll<DProperty>(
+ it.toSourceSetDependent().toAdditionalModifiers(),
+ (psi.annotations.toList().toListOfAnnotations() + it.toListOfAnnotations()).toSourceSetDependent()
+ .toAnnotations()
+ )
+ }
+ )
+ }
+
+ private fun Collection<PsiAnnotation>.toListOfAnnotations() =
+ filter { it !is KtLightAbstractAnnotation }.mapNotNull { it.toAnnotation() }
+
+ private fun JvmAnnotationAttribute.toValue(): AnnotationParameterValue = when (this) {
+ is PsiNameValuePair -> value?.toValue() ?: StringValue("")
+ else -> StringValue(this.attributeName)
+ }
+
+ private fun PsiAnnotationMemberValue.toValue(): AnnotationParameterValue? = when (this) {
+ is PsiAnnotation -> toAnnotation()?.let { AnnotationValue(it) }
+ is PsiArrayInitializerMemberValue -> ArrayValue(initializers.mapNotNull { it.toValue() })
+ is PsiReferenceExpression -> psiReference?.let { EnumValue(text ?: "", DRI.from(it)) }
+ is PsiClassObjectAccessExpression -> {
+ val psiClass = ((type as PsiImmediateClassType).parameters.single() as PsiClassReferenceType).resolve()
+ psiClass?.let { ClassValue(text ?: "", DRI.from(psiClass)) }
+ }
+ else -> StringValue(text ?: "")
+ }
+
+ private fun PsiAnnotation.toAnnotation() = psiReference?.let { psiElement ->
+ Annotations.Annotation(
+ DRI.from(psiElement),
+ attributes.filter { it !is KtLightAbstractAnnotation }.mapNotNull { it.attributeName to it.toValue() }
+ .toMap(),
+ (psiElement as PsiClass).annotations.any {
+ it.hasQualifiedName("java.lang.annotation.Documented")
+ }
+ )
+ }
+
+ private val PsiElement.psiReference
+ get() = getChildOfType<PsiJavaCodeReferenceElement>()?.resolve()
+ }
+
+ private data class AncestorLevel(val level: Int, val superclass: DRI?, val interfaces: List<DRI>)
+}
diff --git a/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt b/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt
new file mode 100644
index 00000000..81955fde
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt
@@ -0,0 +1,216 @@
+package org.jetbrains.dokka.base.translators.psi
+
+import com.intellij.psi.*
+import com.intellij.psi.impl.source.javadoc.PsiDocParamRef
+import com.intellij.psi.impl.source.tree.JavaDocElementType
+import com.intellij.psi.impl.source.tree.LeafPsiElement
+import com.intellij.psi.javadoc.*
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.dokka.analysis.from
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.doc.Deprecated
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
+import org.jetbrains.kotlin.utils.addToStdlib.safeAs
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Element
+import org.jsoup.nodes.Node
+import org.jsoup.nodes.TextNode
+
+interface JavaDocumentationParser {
+ fun parseDocumentation(element: PsiNamedElement): DocumentationNode
+}
+
+class JavadocParser(
+ private val logger: DokkaLogger // TODO: Add logging
+) : JavaDocumentationParser {
+
+ override fun parseDocumentation(element: PsiNamedElement): DocumentationNode {
+ val docComment = findClosestDocComment(element) ?: return DocumentationNode(emptyList())
+ val nodes = mutableListOf<TagWrapper>()
+ docComment.getDescription()?.let { nodes.add(it) }
+ nodes.addAll(docComment.tags.mapNotNull { tag ->
+ when (tag.name) {
+ "param" -> Param(P(convertJavadocElements(tag.dataElements.toList())), tag.text)
+ "throws" -> Throws(P(convertJavadocElements(tag.dataElements.toList())), tag.text)
+ "return" -> Return(P(convertJavadocElements(tag.dataElements.toList())))
+ "author" -> Author(P(convertJavadocElements(tag.dataElements.toList())))
+ "see" -> See(P(getSeeTagElementContent(tag)), tag.referenceElement()?.text.orEmpty(), null)
+ "deprecated" -> Deprecated(P(convertJavadocElements(tag.dataElements.toList())))
+ else -> null
+ }
+ })
+ return DocumentationNode(nodes)
+ }
+
+ private fun findClosestDocComment(element: PsiNamedElement): PsiDocComment? {
+ (element as? PsiDocCommentOwner)?.docComment?.run { return this }
+ if (element is PsiMethod) {
+ val superMethods = element.findSuperMethodsOrEmptyArray()
+ if (superMethods.isEmpty()) return null
+
+ if (superMethods.size == 1) {
+ return findClosestDocComment(superMethods.single())
+ }
+
+ val superMethodDocumentation = superMethods.map(::findClosestDocComment)
+ if (superMethodDocumentation.size == 1) {
+ return superMethodDocumentation.single()
+ }
+
+ logger.warn(
+ "Conflicting documentation for ${DRI.from(element)}" +
+ "${superMethods.map { DRI.from(it) }}"
+ )
+
+ /* Prioritize super class over interface */
+ val indexOfSuperClass = superMethods.indexOfFirst { method ->
+ val parent = method.parent
+ if (parent is PsiClass) !parent.isInterface
+ else false
+ }
+
+ return if (indexOfSuperClass >= 0) superMethodDocumentation[indexOfSuperClass]
+ else superMethodDocumentation.first()
+ }
+
+ return null
+ }
+
+ /**
+ * Workaround for failing [PsiMethod.findSuperMethods].
+ * This might be resolved once ultra light classes are enabled for dokka
+ * See [KT-39518](https://youtrack.jetbrains.com/issue/KT-39518)
+ */
+ private fun PsiMethod.findSuperMethodsOrEmptyArray(): Array<PsiMethod> {
+ return try {
+ /*
+ We are not even attempting to call "findSuperMethods" on all methods called "getGetter" or "getSetter"
+ on any object implementing "kotlin.reflect.KProperty", since we know that those methods will fail
+ (KT-39518). Just catching the exception is not good enough, since "findSuperMethods" will
+ print the whole exception to stderr internally and then spoil the console.
+ */
+ val kPropertyFqName = FqName("kotlin.reflect.KProperty")
+ if (
+ this.parent?.safeAs<PsiClass>()?.implementsInterface(kPropertyFqName) == true &&
+ (this.name == "getSetter" || this.name == "getGetter")
+ ) {
+ logger.warn("Skipped lookup of super methods for ${getKotlinFqName()} (KT-39518)")
+ return emptyArray()
+ }
+ findSuperMethods()
+ } catch (exception: Throwable) {
+ logger.warn("Failed to lookup of super methods for ${getKotlinFqName()} (KT-39518)")
+ emptyArray()
+ }
+ }
+
+ private fun PsiClass.implementsInterface(fqName: FqName): Boolean {
+ return allInterfaces().any { it.getKotlinFqName() == fqName }
+ }
+
+ private fun PsiClass.allInterfaces(): Sequence<PsiClass> {
+ return sequence {
+ this.yieldAll(interfaces.toList())
+ interfaces.forEach { yieldAll(it.allInterfaces()) }
+ }
+ }
+
+ private fun getSeeTagElementContent(tag: PsiDocTag): List<DocTag> =
+ listOfNotNull(tag.referenceElement()?.toDocumentationLink())
+
+ private fun PsiDocComment.getDescription(): Description? {
+ val nonEmptyDescriptionElements = descriptionElements.filter { it.text.trim().isNotEmpty() }
+ val convertedDescriptionElements = convertJavadocElements(nonEmptyDescriptionElements)
+ if (convertedDescriptionElements.isNotEmpty()) {
+ return Description(P(convertedDescriptionElements))
+ }
+
+ return null
+ }
+
+ private fun convertJavadocElements(elements: Iterable<PsiElement>): List<DocTag> =
+ elements.mapNotNull {
+ when (it) {
+ is PsiReference -> convertJavadocElements(it.children.toList())
+ is PsiInlineDocTag -> listOfNotNull(convertInlineDocTag(it))
+ is PsiDocParamRef -> listOfNotNull(it.toDocumentationLink())
+ is PsiDocTagValue,
+ is LeafPsiElement -> Jsoup.parse(it.text.trim()).body().childNodes().mapNotNull(::convertHtmlNode)
+ else -> null
+ }
+ }.flatten()
+
+ private fun convertHtmlNode(node: Node, insidePre: Boolean = false): DocTag? = when (node) {
+ is TextNode -> Text(body = if (insidePre) node.wholeText else node.text())
+ is Element -> createBlock(node)
+ else -> null
+ }
+
+ private fun createBlock(element: Element): DocTag {
+ val children = element.childNodes().mapNotNull { convertHtmlNode(it) }
+ return when (element.tagName()) {
+ "p" -> P(listOf(Br, Br) + children)
+ "b" -> B(children)
+ "strong" -> Strong(children)
+ "i" -> I(children)
+ "em" -> Em(children)
+ "code" -> CodeBlock(children)
+ "pre" -> Pre(children)
+ "ul" -> Ul(children)
+ "ol" -> Ol(children)
+ "li" -> Li(children)
+ "a" -> createLink(element, children)
+ else -> Text(body = element.ownText())
+ }
+ }
+
+ private fun createLink(element: Element, children: List<DocTag>): DocTag {
+ return when {
+ element.hasAttr("docref") -> {
+ A(children, params = mapOf("docref" to element.attr("docref")))
+ }
+ element.hasAttr("href") -> {
+ A(children, params = mapOf("href" to element.attr("href")))
+ }
+ else -> Text(children = children)
+ }
+ }
+
+ private fun PsiDocToken.isSharpToken() = tokenType.toString() == "DOC_TAG_VALUE_SHARP_TOKEN"
+
+ private fun PsiElement.toDocumentationLink(labelElement: PsiElement? = null) =
+ reference?.resolve()?.let {
+ val dri = DRI.from(it)
+ val label = labelElement ?: children.firstOrNull {
+ it is PsiDocToken && it.text.isNotBlank() && !it.isSharpToken()
+ } ?: this
+ DocumentationLink(dri, convertJavadocElements(listOfNotNull(label)))
+ }
+
+ private fun convertInlineDocTag(tag: PsiInlineDocTag) = when (tag.name) {
+ "link", "linkplain" -> {
+ tag.referenceElement()?.toDocumentationLink(tag.dataElements.firstIsInstanceOrNull<PsiDocToken>())
+ }
+ "code", "literal" -> {
+ CodeInline(listOf(Text(tag.text)))
+ }
+ "index" -> Index(tag.children.filterIsInstance<PsiDocTagValue>().map { Text(it.text) })
+ else -> Text(tag.text)
+ }
+
+ private fun PsiDocTag.referenceElement(): PsiElement? =
+ linkElement()?.let {
+ if (it.node.elementType == JavaDocElementType.DOC_REFERENCE_HOLDER) {
+ PsiTreeUtil.findChildOfType(it, PsiJavaCodeReferenceElement::class.java)
+ } else {
+ it
+ }
+ }
+
+ private fun PsiDocTag.linkElement(): PsiElement? =
+ valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace }
+}