diff options
6 files changed, 229 insertions, 65 deletions
diff --git a/core/src/main/kotlin/model/Documentable.kt b/core/src/main/kotlin/model/Documentable.kt index 48f00474..73a5f9d3 100644 --- a/core/src/main/kotlin/model/Documentable.kt +++ b/core/src/main/kotlin/model/Documentable.kt @@ -3,6 +3,8 @@ package org.jetbrains.dokka.model import com.intellij.psi.PsiNamedElement import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.dokka.model.properties.ExtraProperty +import org.jetbrains.dokka.model.properties.MergeStrategy import org.jetbrains.dokka.model.properties.PropertyContainer import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.pages.PlatformData @@ -72,10 +74,10 @@ interface WithType { } interface WithAbstraction { - val modifier: Modifier + val modifier: Modifier? enum class Modifier { - Abstract, Open, Final + Abstract, Open, Final, Static } } @@ -145,7 +147,7 @@ data class Class( override val generics: List<TypeParameter>, override val supertypes: PlatformDependent<List<DRI>>, override val documentation: PlatformDependent<DocumentationNode>, - override val modifier: WithAbstraction.Modifier, + override val modifier: WithAbstraction.Modifier?, override val platformData: List<PlatformData>, override val extra: PropertyContainer<Class> = PropertyContainer.empty() ) : Classlike(), WithAbstraction, WithCompanion, WithConstructors, WithGenerics, WithSupertypes, @@ -206,7 +208,7 @@ data class Function( override val type: TypeWrapper, override val generics: List<TypeParameter>, override val receiver: Parameter?, - override val modifier: WithAbstraction.Modifier, + override val modifier: WithAbstraction.Modifier?, override val platformData: List<PlatformData>, override val extra: PropertyContainer<Function> = PropertyContainer.empty() ) : Documentable(), Callable, WithGenerics, WithExtraProperties<Function> { @@ -285,8 +287,8 @@ data class Property( override val type: TypeWrapper, override val receiver: Parameter?, val setter: Function?, - val getter: Function, - override val modifier: WithAbstraction.Modifier, + val getter: Function?, + override val modifier: WithAbstraction.Modifier?, override val platformData: List<PlatformData>, override val extra: PropertyContainer<Property> = PropertyContainer.empty() ) : Documentable(), Callable, WithExtraProperties<Property> { @@ -348,4 +350,4 @@ sealed class DocumentableSource(val path: String) class DescriptorDocumentableSource(val descriptor: DeclarationDescriptor) : DocumentableSource(descriptor.toSourceElement.containingFile.toString()) -class PsiDocumentableSource(val psi: PsiNamedElement) : DocumentableSource(psi.containingFile.virtualFile.path)
\ No newline at end of file +class PsiDocumentableSource(val psi: PsiNamedElement) : DocumentableSource(psi.containingFile.virtualFile.path) diff --git a/core/src/main/kotlin/model/documentableProperties.kt b/core/src/main/kotlin/model/documentableProperties.kt new file mode 100644 index 00000000..38a06451 --- /dev/null +++ b/core/src/main/kotlin/model/documentableProperties.kt @@ -0,0 +1,14 @@ +package org.jetbrains.dokka.model + +import org.jetbrains.dokka.model.properties.ExtraProperty +import org.jetbrains.dokka.model.properties.MergeStrategy + +data class InheritedFunction(val isInherited: Boolean): ExtraProperty<Function> { + object InheritedFunctionKey: ExtraProperty.Key<Function, Boolean> { + override fun mergeStrategyFor(left: Boolean, right: Boolean) = MergeStrategy.Fail { + throw IllegalArgumentException("Function inheritance should be consistent!") + } + } + override val key: ExtraProperty.Key<Function, Boolean> = + InheritedFunctionKey +}
\ No newline at end of file diff --git a/core/src/main/kotlin/model/properties/PropertyContainer.kt b/core/src/main/kotlin/model/properties/PropertyContainer.kt index 50216278..d30c6844 100644 --- a/core/src/main/kotlin/model/properties/PropertyContainer.kt +++ b/core/src/main/kotlin/model/properties/PropertyContainer.kt @@ -1,16 +1,15 @@ package org.jetbrains.dokka.model.properties -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull class PropertyContainer<C : Any> internal constructor( - @PublishedApi internal val map: Map<Property.Key<C, *>, Property<C>> + @PublishedApi internal val map: Map<ExtraProperty.Key<C, *>, ExtraProperty<C>> ) { - operator fun <D : C> plus(prop: Property<D>): PropertyContainer<D> = + operator fun <D : C> plus(prop: ExtraProperty<D>): PropertyContainer<D> = PropertyContainer(map + (prop.key to prop)) // TODO: Add logic for caching calculated properties - inline operator fun <reified T : Any> get(key: Property.Key<C, T>): T? = when (val prop = map[key]) { + inline operator fun <reified T : Any> get(key: ExtraProperty.Key<C, T>): T? = when (val prop = map[key]) { is T? -> prop else -> throw ClassCastException("Property for $key stored under not matching key type.") } @@ -27,7 +26,7 @@ interface WithExtraProperties<C : Any> { } fun <C> C.mergeExtras(left: C, right: C): C where C : Any, C : WithExtraProperties<C> { - val aggregatedExtras: List<List<Property<C>>> = + val aggregatedExtras: List<List<ExtraProperty<C>>> = (left.extra.map.values + right.extra.map.values) .groupBy { it.key } .values @@ -37,12 +36,12 @@ fun <C> C.mergeExtras(left: C, right: C): C where C : Any, C : WithExtraProperti @Suppress("UNCHECKED_CAST") val strategies: List<MergeStrategy<C>> = toMerge.map { (l, r) -> - (l.key as Property.Key<C, Property<C>>).mergeStrategyFor(l, r) + (l.key as ExtraProperty.Key<C, ExtraProperty<C>>).mergeStrategyFor(l, r) } strategies.firstIsInstanceOrNull<MergeStrategy.Fail>()?.error?.invoke() - val replaces: List<Property<C>> = strategies.filterIsInstance<MergeStrategy.Replace<C>>().map { it.newProperty } + val replaces: List<ExtraProperty<C>> = strategies.filterIsInstance<MergeStrategy.Replace<C>>().map { it.newProperty } val needingFullMerge: List<(preMerged: C, left: C, right: C) -> C> = strategies.filterIsInstance<MergeStrategy.Full<C>>().map { it.merger } diff --git a/core/src/main/kotlin/model/properties/properties.kt b/core/src/main/kotlin/model/properties/properties.kt index 4ca44926..83f8d63d 100644 --- a/core/src/main/kotlin/model/properties/properties.kt +++ b/core/src/main/kotlin/model/properties/properties.kt @@ -1,6 +1,6 @@ package org.jetbrains.dokka.model.properties -interface Property<in C : Any> { +interface ExtraProperty<in C : Any> { interface Key<in C: Any, T: Any> { fun mergeStrategyFor(left: T, right: T): MergeStrategy<C> = MergeStrategy.Fail { throw NotImplementedError("Property merging for $this is not implemented") @@ -9,12 +9,12 @@ interface Property<in C : Any> { val key: Key<C, *> } -interface CalculatedProperty<in C: Any, T: Any>: Property.Key<C, T> { +interface CalculatedProperty<in C: Any, T: Any>: ExtraProperty.Key<C, T> { fun calculate(subject: C): T } sealed class MergeStrategy<in C> { - class Replace<in C : Any>(val newProperty: Property<C>): MergeStrategy<C>() + class Replace<in C : Any>(val newProperty: ExtraProperty<C>): MergeStrategy<C>() object Remove: MergeStrategy<Any>() class Full<C: Any>(val merger: (preMerged: C, left: C, right: C) -> C): MergeStrategy<C>() class Fail(val error: () -> Nothing): MergeStrategy<Any>() diff --git a/core/src/main/kotlin/model/typeWrappers.kt b/core/src/main/kotlin/model/typeWrappers.kt index 5719c6d3..b26a3f6d 100644 --- a/core/src/main/kotlin/model/typeWrappers.kt +++ b/core/src/main/kotlin/model/typeWrappers.kt @@ -94,4 +94,8 @@ class JavaTypeWrapper : TypeWrapper { override fun toString(): String { return constructorFqName.orEmpty() } + + companion object { + val VOID = JavaTypeWrapper(listOf("void"), listOf(), null, true) + } } diff --git a/plugins/base/src/main/kotlin/transformers/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/transformers/psi/DefaultPsiToDocumentableTranslator.kt index 364cb34b..da06e621 100644 --- a/plugins/base/src/main/kotlin/transformers/psi/DefaultPsiToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/transformers/psi/DefaultPsiToDocumentableTranslator.kt @@ -1,5 +1,7 @@ package org.jetbrains.dokka.base.transformers.psi +import com.intellij.lang.jvm.JvmModifier +import com.intellij.lang.jvm.types.JvmReferenceType import com.intellij.psi.* import org.jetbrains.dokka.JavadocParser import org.jetbrains.dokka.links.Callable @@ -7,7 +9,11 @@ import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.links.JavaClassReference import org.jetbrains.dokka.links.withClass import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.Annotation +import org.jetbrains.dokka.model.Enum import org.jetbrains.dokka.model.Function +import org.jetbrains.dokka.model.InheritedFunction +import org.jetbrains.dokka.model.properties.PropertyContainer import org.jetbrains.dokka.pages.PlatformData import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.transformers.psi.PsiToDocumentableTranslator @@ -27,16 +33,22 @@ object DefaultPsiToDocumentableTranslator : PsiToDocumentableTranslator { platformData, context.logger ) - return Module(moduleName, + return Module( + moduleName, psiFiles.map { psiFile -> val dri = DRI(packageName = psiFile.packageName) Package( dri, emptyList(), emptyList(), - psiFile.classes.map { docParser.parseClass(it, dri) } + psiFile.classes.map { docParser.parseClasslike(it, dri) }, + emptyList(), + PlatformDependent.empty(), + listOf(platformData) ) - } + }, + PlatformDependent.empty(), + listOf(platformData) ) } @@ -47,11 +59,6 @@ object DefaultPsiToDocumentableTranslator : PsiToDocumentableTranslator { private val javadocParser: JavadocParser = JavadocParser(logger) - private fun getComment(psi: PsiNamedElement): List<PlatformInfo> { - val comment = javadocParser.parseDocumentation(psi) - return listOf(BasePlatformInfo(comment, listOf(platformData))) - } - private fun PsiModifierListOwner.getVisibility() = modifierList?.children?.toList()?.let { ml -> when { ml.any { it.text == PsiKeyword.PUBLIC } -> Visibilities.PUBLIC @@ -60,69 +67,203 @@ object DefaultPsiToDocumentableTranslator : PsiToDocumentableTranslator { } } ?: Visibilities.PRIVATE - fun parseClass(psi: PsiClass, parent: DRI): Class = with(psi) { - val kind = when { - isAnnotationType -> JavaClassKindTypes.ANNOTATION_CLASS - isInterface -> JavaClassKindTypes.INTERFACE - isEnum -> JavaClassKindTypes.ENUM_CLASS - else -> JavaClassKindTypes.CLASS + 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.toPlatformDependant() = + PlatformDependent(mapOf(platformData to this)) + + fun parseClasslike(psi: PsiClass, parent: DRI): Classlike = with(psi) { val dri = parent.withClass(name.toString()) - /*superTypes.filter { !ignoreSupertype(it) }.forEach { - node.appendType(it, NodeKind.Supertype) - val superClass = it.resolve() - if (superClass != null) { - link(superClass, node, RefKind.Inheritor) + val ancestorsSet = hashSetOf<DRI>() + val superMethodsKeys = hashSetOf<Int>() + val superMethods = mutableListOf<PsiMethod>() + methods.forEach { superMethodsKeys.add(it.hash) } + fun addAncestors(element: PsiClass) { + ancestorsSet.add(element.toDRI()) + element.interfaces.forEach(::addAncestors) + element.superClass?.let(::addAncestors) + } + + fun parseSupertypes(superTypes: Array<PsiClassType>) { + superTypes.forEach { type -> + (type as? PsiClassType)?.takeUnless { type.shouldBeIgnored }?.resolve()?.let { + it.methods.forEach { method -> + val hash = method.hash + if (!method.isConstructor && !superMethodsKeys.contains(hash) && + method.getVisibility() != Visibilities.PRIVATE + ) { + superMethodsKeys.add(hash) + superMethods.add(method) + } + } + addAncestors(it) + parseSupertypes(it.superTypes) + } + } + } + parseSupertypes(superTypes) + val documentation = javadocParser.parseDocumentation(this).toPlatformDependant() + val allFunctions = methods.mapNotNull { if (!it.isConstructor) parseFunction(it, dri) else null } + + superMethods.map { parseFunction(it, dri, isInherited = true) } + val source = PsiDocumentableSource(this).toPlatformDependant() + val classlikes = innerClasses.map { parseClasslike(it, dri) } + val visibility = getVisibility().toPlatformDependant() + val ancestors = ancestorsSet.toList().toPlatformDependant() + return when { + isAnnotationType -> + Annotation( + name.orEmpty(), + dri, + documentation, + source, + allFunctions, + fields.mapNotNull { parseField(it, dri) }, + classlikes, + visibility, + null, + constructors.map { parseFunction(it, dri, true) }, + listOf(platformData) + ) + isEnum -> Enum( + dri, + name.orEmpty(), + fields.filterIsInstance<PsiEnumConstant>().map { entry -> + EnumEntry( + dri.withClass("$name.${entry.name}"), + entry.name.orEmpty(), + javadocParser.parseDocumentation(entry).toPlatformDependant(), + emptyList(), + emptyList(), + emptyList(), + listOf(platformData) + ) + }, + documentation, + source, + allFunctions, + fields.filter { it !is PsiEnumConstant }.map { parseField(it, dri) }, + classlikes, + visibility, + null, + constructors.map { parseFunction(it, dri, true) }, + ancestors, + listOf(platformData) + ) + isInterface -> Interface( + dri, + name.orEmpty(), + documentation, + source, + allFunctions, + fields.mapNotNull { parseField(it, dri) }, + classlikes, + visibility, + null, + mapTypeParameters(dri), + ancestors, + listOf(platformData) + ) + else -> Class( + dri, + name.orEmpty(), + constructors.map { parseFunction(it, dri, true) }, + allFunctions, + fields.mapNotNull { parseField(it, dri) }, + classlikes, + source, + visibility, + null, + mapTypeParameters(dri), + ancestors, + documentation, + getModifier(), + listOf(platformData) + ) } - }*/ - val inherited = emptyList<DRI>() //listOf(psi.superClass) + psi.interfaces // TODO DRIs of inherited - val actual = getComment(psi).map { ClassPlatformInfo(it, inherited) } - - return Class( - dri = dri, - name = name.orEmpty(), - kind = kind, - constructors = constructors.map { parseFunction(it, dri, true) }, - functions = methods.mapNotNull { if (!it.isConstructor) parseFunction(it, dri) else null }, - properties = fields.mapNotNull { parseField(it, dri) }, - classlikes = innerClasses.map { parseClass(it, dri) }, - expected = null, - actual = actual, - extra = mutableSetOf(), - visibility = mapOf(platformData to psi.getVisibility()) - ) } - private fun parseFunction(psi: PsiMethod, parent: DRI, isConstructor: Boolean = false): Function { + private fun parseFunction( + psi: PsiMethod, + parent: DRI, + isConstructor: Boolean = false, + isInherited: Boolean = false + ): Function { val dri = parent.copy(callable = Callable( psi.name, JavaClassReference(psi.containingClass?.name.orEmpty()), psi.parameterList.parameters.map { parameter -> JavaClassReference(parameter.type.canonicalText) - } - ) + }) ) return Function( dri, if (isConstructor) "<init>" else psi.name, - psi.returnType?.let { JavaTypeWrapper(type = it) }, isConstructor, - null, psi.parameterList.parameters.mapIndexed { index, psiParameter -> Parameter( dri.copy(target = index + 1), psiParameter.name, + javadocParser.parseDocumentation(psiParameter).toPlatformDependant(), JavaTypeWrapper(psiParameter.type), - null, - getComment(psi) + listOf(platformData) ) }, + javadocParser.parseDocumentation(psi).toPlatformDependant(), + PsiDocumentableSource(psi).toPlatformDependant(), + psi.getVisibility().toPlatformDependant(), + psi.returnType?.let { JavaTypeWrapper(type = it) } ?: JavaTypeWrapper.VOID, + psi.mapTypeParameters(dri), null, - getComment(psi), - visibility = mapOf(platformData to psi.getVisibility()) + psi.getModifier(), + listOf(platformData), + PropertyContainer.empty<Function>() + InheritedFunction( + isInherited + ) + ) } + private fun PsiModifierListOwner.getModifier() = when { + hasModifier(JvmModifier.ABSTRACT) -> WithAbstraction.Modifier.Abstract + hasModifier(JvmModifier.STATIC) -> WithAbstraction.Modifier.Static + hasModifier(JvmModifier.FINAL) -> WithAbstraction.Modifier.Final + else -> null + } + + private fun PsiTypeParameterListOwner.mapTypeParameters(dri: DRI): List<TypeParameter> { + fun mapProjections(bounds: Array<JvmReferenceType>) = + if (bounds.isEmpty()) listOf(Projection.Star) else bounds.mapNotNull { + (it as PsiClassType).let { classType -> + Projection.Nullable(Projection.TypeConstructor(classType.resolve()!!.toDRI(), emptyList())) + } + } + return typeParameters.mapIndexed { index, type -> + TypeParameter( + dri.copy(genericTarget = index), + type.name.orEmpty(), + javadocParser.parseDocumentation(type).toPlatformDependant(), + mapProjections(type.bounds), + listOf(platformData) + ) + } + } + + private fun PsiQualifiedNamedElement.toDRI() = + DRI(qualifiedName.orEmpty().substringBeforeLast('.', ""), name) + private fun parseField(psi: PsiField, parent: DRI): Property { val dri = parent.copy( callable = Callable( @@ -134,11 +275,15 @@ object DefaultPsiToDocumentableTranslator : PsiToDocumentableTranslator { return Property( dri, psi.name!!, // TODO: Investigate if this is indeed nullable + javadocParser.parseDocumentation(psi).toPlatformDependant(), + PsiDocumentableSource(psi).toPlatformDependant(), + psi.getVisibility().toPlatformDependant(), + JavaTypeWrapper(psi.type), + null, null, null, - getComment(psi), - accessors = emptyList(), - visibility = mapOf(platformData to psi.getVisibility()) + psi.getModifier(), + listOf(platformData) ) } } |