From b62126328f0778abe21d3334f5a713086cb3d15e Mon Sep 17 00:00:00 2001 From: Błażej Kardyś Date: Wed, 26 Feb 2020 15:51:07 +0100 Subject: Adding psi parsing for new model --- core/src/main/kotlin/model/Documentable.kt | 16 +- .../main/kotlin/model/documentableProperties.kt | 14 ++ .../kotlin/model/properties/PropertyContainer.kt | 13 +- .../src/main/kotlin/model/properties/properties.kt | 6 +- core/src/main/kotlin/model/typeWrappers.kt | 4 + .../psi/DefaultPsiToDocumentableTranslator.kt | 241 +++++++++++++++++---- 6 files changed, 229 insertions(+), 65 deletions(-) create mode 100644 core/src/main/kotlin/model/documentableProperties.kt 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, override val supertypes: PlatformDependent>, override val documentation: PlatformDependent, - override val modifier: WithAbstraction.Modifier, + override val modifier: WithAbstraction.Modifier?, override val platformData: List, override val extra: PropertyContainer = PropertyContainer.empty() ) : Classlike(), WithAbstraction, WithCompanion, WithConstructors, WithGenerics, WithSupertypes, @@ -206,7 +208,7 @@ data class Function( override val type: TypeWrapper, override val generics: List, override val receiver: Parameter?, - override val modifier: WithAbstraction.Modifier, + override val modifier: WithAbstraction.Modifier?, override val platformData: List, override val extra: PropertyContainer = PropertyContainer.empty() ) : Documentable(), Callable, WithGenerics, WithExtraProperties { @@ -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, override val extra: PropertyContainer = PropertyContainer.empty() ) : Documentable(), Callable, WithExtraProperties { @@ -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 { + object InheritedFunctionKey: ExtraProperty.Key { + override fun mergeStrategyFor(left: Boolean, right: Boolean) = MergeStrategy.Fail { + throw IllegalArgumentException("Function inheritance should be consistent!") + } + } + override val key: ExtraProperty.Key = + 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 internal constructor( - @PublishedApi internal val map: Map, Property> + @PublishedApi internal val map: Map, ExtraProperty> ) { - operator fun plus(prop: Property): PropertyContainer = + operator fun plus(prop: ExtraProperty): PropertyContainer = PropertyContainer(map + (prop.key to prop)) // TODO: Add logic for caching calculated properties - inline operator fun get(key: Property.Key): T? = when (val prop = map[key]) { + inline operator fun get(key: ExtraProperty.Key): 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 { } fun C.mergeExtras(left: C, right: C): C where C : Any, C : WithExtraProperties { - val aggregatedExtras: List>> = + val aggregatedExtras: List>> = (left.extra.map.values + right.extra.map.values) .groupBy { it.key } .values @@ -37,12 +36,12 @@ fun C.mergeExtras(left: C, right: C): C where C : Any, C : WithExtraProperti @Suppress("UNCHECKED_CAST") val strategies: List> = toMerge.map { (l, r) -> - (l.key as Property.Key>).mergeStrategyFor(l, r) + (l.key as ExtraProperty.Key>).mergeStrategyFor(l, r) } strategies.firstIsInstanceOrNull()?.error?.invoke() - val replaces: List> = strategies.filterIsInstance>().map { it.newProperty } + val replaces: List> = strategies.filterIsInstance>().map { it.newProperty } val needingFullMerge: List<(preMerged: C, left: C, right: C) -> C> = strategies.filterIsInstance>().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 { +interface ExtraProperty { interface Key { fun mergeStrategyFor(left: T, right: T): MergeStrategy = MergeStrategy.Fail { throw NotImplementedError("Property merging for $this is not implemented") @@ -9,12 +9,12 @@ interface Property { val key: Key } -interface CalculatedProperty: Property.Key { +interface CalculatedProperty: ExtraProperty.Key { fun calculate(subject: C): T } sealed class MergeStrategy { - class Replace(val newProperty: Property): MergeStrategy() + class Replace(val newProperty: ExtraProperty): MergeStrategy() object Remove: MergeStrategy() class Full(val merger: (preMerged: C, left: C, right: C) -> C): MergeStrategy() class Fail(val error: () -> Nothing): MergeStrategy() 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 { - 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.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() + val superMethodsKeys = hashSetOf() + val superMethods = mutableListOf() + 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) { + 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().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() //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) "" 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() + 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 { + fun mapProjections(bounds: Array) = + 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) ) } } -- cgit