From c4c6a165d968fefbf8caa52b9c9763b58fc39803 Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Tue, 11 Feb 2020 14:16:11 +0100 Subject: Creates dokka base --- plugins/base/build.gradle.kts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 plugins/base/build.gradle.kts (limited to 'plugins/base/build.gradle.kts') diff --git a/plugins/base/build.gradle.kts b/plugins/base/build.gradle.kts new file mode 100644 index 00000000..386a3288 --- /dev/null +++ b/plugins/base/build.gradle.kts @@ -0,0 +1,8 @@ +publishing { + publications { + register("basePlugin") { + artifactId = "dokka-base" + from(components["java"]) + } + } +} \ No newline at end of file -- cgit From 848f2e0656e80604cb54932db5b250303aaccca8 Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Wed, 12 Feb 2020 16:01:38 +0100 Subject: Moves DescriptorToDocumentableTransformer to base plugin --- core/src/main/kotlin/CoreExtensions.kt | 1 - core/src/main/kotlin/DokkaGenerator.kt | 2 +- core/src/main/kotlin/model/Documentable.kt | 12 - core/src/main/kotlin/model/classKinds.kt | 25 ++ core/src/main/kotlin/model/typeWrappers.kt | 97 ++++++ .../main/kotlin/plugability/DefaultExtensions.kt | 2 - .../DefaultDescriptorToDocumentationTranslator.kt | 362 --------------------- .../DescriptorToDocumentationTranslator.kt | 3 +- .../psi/DefaultPsiToDocumentationTranslator.kt | 72 ---- plugins/base/build.gradle.kts | 3 +- plugins/base/src/main/kotlin/DokkaBase.kt | 7 + .../DefaultDescriptorToDocumentationTranslator.kt | 338 +++++++++++++++++++ plugins/kotlin-as-java/build.gradle.kts | 4 + ...linAsJavaDescriptorToDocumentationTranslator.kt | 13 +- .../src/main/kotlin/KotlinAsJavaPageBuilder.kt | 1 - .../main/kotlin/KotlinAsJavaPageContentBuilder.kt | 2 +- .../src/main/kotlin/KotlinAsJavaPlugin.kt | 8 +- .../src/main/kotlin/KotlinToJVMResolver.kt | 1 - 18 files changed, 489 insertions(+), 464 deletions(-) create mode 100644 core/src/main/kotlin/model/classKinds.kt create mode 100644 core/src/main/kotlin/model/typeWrappers.kt delete mode 100644 core/src/main/kotlin/transformers/descriptors/DefaultDescriptorToDocumentationTranslator.kt create mode 100644 plugins/base/src/main/kotlin/transformers/descriptors/DefaultDescriptorToDocumentationTranslator.kt (limited to 'plugins/base/build.gradle.kts') diff --git a/core/src/main/kotlin/CoreExtensions.kt b/core/src/main/kotlin/CoreExtensions.kt index 18cde210..1364f1c1 100644 --- a/core/src/main/kotlin/CoreExtensions.kt +++ b/core/src/main/kotlin/CoreExtensions.kt @@ -30,7 +30,6 @@ object CoreExtensions { val locationProviderFactory by coreExtension() val outputWriter by coreExtension() val renderer by coreExtension() - val fileExtension by coreExtension() val pageMergerStrategy by coreExtension() private fun coreExtension() = object { diff --git a/core/src/main/kotlin/DokkaGenerator.kt b/core/src/main/kotlin/DokkaGenerator.kt index a31c04a6..6a21e031 100644 --- a/core/src/main/kotlin/DokkaGenerator.kt +++ b/core/src/main/kotlin/DokkaGenerator.kt @@ -130,7 +130,7 @@ class DokkaGenerator( .toList() return context.single(CoreExtensions.descriptorToDocumentationTranslator) - .invoke(platformData.name, packageFragments, platformData, context) + .invoke(platformData.name, packageFragments, platformData) } private fun translatePsi(platformData: PlatformData, context: DokkaContext): Module { diff --git a/core/src/main/kotlin/model/Documentable.kt b/core/src/main/kotlin/model/Documentable.kt index e64f82ac..f0820256 100644 --- a/core/src/main/kotlin/model/Documentable.kt +++ b/core/src/main/kotlin/model/Documentable.kt @@ -3,9 +3,6 @@ package org.jetbrains.dokka.model import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.doc.DocumentationNode import org.jetbrains.dokka.pages.PlatformData -import org.jetbrains.dokka.transformers.descriptors.KotlinClassKindTypes -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibility class Module(override val name: String, val packages: List) : Documentable() { @@ -239,15 +236,6 @@ private fun String.shorten(maxLength: Int) = lineSequence().first().let { if (it.length != length || it.length > maxLength) it.take(maxLength - 3) + "..." else it } -interface TypeWrapper { - val constructorFqName: String? - val constructorNamePathSegments: List - val arguments: List - val dri: DRI? -} - -interface ClassKind - fun Documentable.dfs(predicate: (Documentable) -> Boolean): Documentable? = if (predicate(this)) { this diff --git a/core/src/main/kotlin/model/classKinds.kt b/core/src/main/kotlin/model/classKinds.kt new file mode 100644 index 00000000..2548f68d --- /dev/null +++ b/core/src/main/kotlin/model/classKinds.kt @@ -0,0 +1,25 @@ +package org.jetbrains.dokka.model + +import org.jetbrains.dokka.links.DRI +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.types.KotlinType + + +interface ClassKind + +enum class KotlinClassKindTypes : ClassKind { + CLASS, + INTERFACE, + ENUM_CLASS, + ENUM_ENTRY, + ANNOTATION_CLASS, + OBJECT; +} + +enum class JavaClassKindTypes : ClassKind { + CLASS, + INTERFACE, + ENUM_CLASS, + ENUM_ENTRY, + ANNOTATION_CLASS; +} diff --git a/core/src/main/kotlin/model/typeWrappers.kt b/core/src/main/kotlin/model/typeWrappers.kt new file mode 100644 index 00000000..5719c6d3 --- /dev/null +++ b/core/src/main/kotlin/model/typeWrappers.kt @@ -0,0 +1,97 @@ +package org.jetbrains.dokka.model + +import com.intellij.psi.PsiArrayType +import com.intellij.psi.PsiEllipsisType +import com.intellij.psi.PsiPrimitiveType +import com.intellij.psi.PsiType +import com.intellij.psi.impl.source.PsiClassReferenceType +import org.jetbrains.dokka.links.DRI +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.types.KotlinType + +interface TypeWrapper { + val constructorFqName: String? + val constructorNamePathSegments: List + val arguments: List + val dri: DRI? +} + +class KotlinTypeWrapper(private val kotlinType: KotlinType) : TypeWrapper { + private val declarationDescriptor = kotlinType.constructor.declarationDescriptor + private val fqNameSafe = declarationDescriptor?.fqNameSafe + override val constructorFqName = fqNameSafe?.asString() + override val constructorNamePathSegments: List = + fqNameSafe?.pathSegments()?.map { it.asString() } ?: emptyList() + override val arguments: List by lazy { + kotlinType.arguments.map { + KotlinTypeWrapper( + it.type + ) + } + } + override val dri: DRI? by lazy { declarationDescriptor?.let { DRI.from(it) } } +} + +class JavaTypeWrapper : TypeWrapper { + + override val constructorFqName: String? + override val constructorNamePathSegments: List + override val arguments: List + override val dri: DRI? + val isPrimitive: Boolean + + constructor( + constructorNamePathSegments: List, + arguments: List, + dri: DRI?, + isPrimitiveType: Boolean + ) { + this.constructorFqName = constructorNamePathSegments.joinToString(".") + this.constructorNamePathSegments = constructorNamePathSegments + this.arguments = arguments + this.dri = dri + this.isPrimitive = isPrimitiveType + } + + constructor(type: PsiType) { + if (type is PsiClassReferenceType) { + val resolved = type.resolve() + constructorFqName = resolved?.qualifiedName + constructorNamePathSegments = resolved?.qualifiedName?.split('.') ?: emptyList() + arguments = type.parameters.mapNotNull { + if (it is PsiClassReferenceType) JavaTypeWrapper(it) else null + } + dri = fromPsi(type) + this.isPrimitive = false + } else if (type is PsiEllipsisType) { + constructorFqName = type.canonicalText + constructorNamePathSegments = listOf(type.canonicalText) // TODO + arguments = emptyList() + dri = DRI("java.lang", "Object") // TODO + this.isPrimitive = false + } else if (type is PsiArrayType) { + constructorFqName = type.canonicalText + constructorNamePathSegments = listOf(type.canonicalText) + arguments = emptyList() + dri = (type as? PsiClassReferenceType)?.let { fromPsi(it) } // TODO + this.isPrimitive = false + } else { + type as PsiPrimitiveType + constructorFqName = type.name + constructorNamePathSegments = type.name.split('.') + arguments = emptyList() + dri = null + this.isPrimitive = true + } + } + + private fun fromPsi(type: PsiClassReferenceType): DRI { + val className = type.className + val pkg = type.canonicalText.removeSuffix(className).removeSuffix(".") + return DRI(packageName = pkg, classNames = className) + } + + override fun toString(): String { + return constructorFqName.orEmpty() + } +} diff --git a/core/src/main/kotlin/plugability/DefaultExtensions.kt b/core/src/main/kotlin/plugability/DefaultExtensions.kt index 1320c282..70a7e4db 100644 --- a/core/src/main/kotlin/plugability/DefaultExtensions.kt +++ b/core/src/main/kotlin/plugability/DefaultExtensions.kt @@ -6,7 +6,6 @@ import org.jetbrains.dokka.renderers.FileWriter import org.jetbrains.dokka.renderers.OutputWriter import org.jetbrains.dokka.renderers.html.HtmlRenderer import org.jetbrains.dokka.resolvers.DefaultLocationProviderFactory -import org.jetbrains.dokka.transformers.descriptors.DefaultDescriptorToDocumentationTranslator import org.jetbrains.dokka.transformers.documentation.DefaultDocumentationNodeMerger import org.jetbrains.dokka.transformers.documentation.DefaultDocumentationToPageTranslator import org.jetbrains.dokka.transformers.pages.DefaultPageMergerStrategy @@ -28,7 +27,6 @@ internal object DefaultExtensions { @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST") internal fun > get(point: E, fullContext: DokkaContext): List = when (point) { - CoreExtensions.descriptorToDocumentationTranslator -> DefaultDescriptorToDocumentationTranslator CoreExtensions.psiToDocumentationTranslator -> DefaultPsiToDocumentationTranslator CoreExtensions.documentationMerger -> DefaultDocumentationNodeMerger CoreExtensions.commentsToContentConverter -> converter.get(fullContext) diff --git a/core/src/main/kotlin/transformers/descriptors/DefaultDescriptorToDocumentationTranslator.kt b/core/src/main/kotlin/transformers/descriptors/DefaultDescriptorToDocumentationTranslator.kt deleted file mode 100644 index 173e13ac..00000000 --- a/core/src/main/kotlin/transformers/descriptors/DefaultDescriptorToDocumentationTranslator.kt +++ /dev/null @@ -1,362 +0,0 @@ -package org.jetbrains.dokka.transformers.descriptors - -import org.jetbrains.dokka.analysis.DokkaResolutionFacade -import org.jetbrains.dokka.links.Callable -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.links.withClass -import org.jetbrains.dokka.model.* -import org.jetbrains.dokka.model.ClassKind -import org.jetbrains.dokka.model.Enum -import org.jetbrains.dokka.model.Function -import org.jetbrains.dokka.model.Property -import org.jetbrains.dokka.model.doc.* -import org.jetbrains.dokka.pages.PlatformData -import org.jetbrains.dokka.parsers.MarkdownParser -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBodies -import org.jetbrains.kotlin.idea.kdoc.findKDoc -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe -import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny -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.types.KotlinType -import kotlin.reflect.KClass - -object DefaultDescriptorToDocumentationTranslator : DescriptorToDocumentationTranslator { - override fun invoke( - moduleName: String, - packageFragments: Iterable, - platformData: PlatformData, - context: DokkaContext - ) = DokkaDescriptorVisitor(platformData, context.platforms[platformData]?.facade!!).run { - packageFragments.map { - visitPackageFragmentDescriptor( - it, - DRIWithPlatformInfo(DRI.topLevel, null, emptyList()) - ) - } - }.let { Module(moduleName, it) } - -} - - -data class DRIWithPlatformInfo( - val dri: DRI, - val expected: PlatformInfo?, - val actual: List -) - -fun DRI.withEmptyInfo() = DRIWithPlatformInfo(this, null, emptyList()) - -open class DokkaDescriptorVisitor( - private val platformData: PlatformData, - private val resolutionFacade: DokkaResolutionFacade -) : DeclarationDescriptorVisitorEmptyBodies() { - override fun visitDeclarationDescriptor(descriptor: DeclarationDescriptor, parent: DRIWithPlatformInfo): Nothing { - throw IllegalStateException("${javaClass.simpleName} should never enter ${descriptor.javaClass.simpleName}") - } - - override fun visitPackageFragmentDescriptor( - descriptor: PackageFragmentDescriptor, - parent: DRIWithPlatformInfo - ): Package { - val driWithPlatform = DRI(packageName = descriptor.fqName.asString()).withEmptyInfo() - val scope = descriptor.getMemberScope() - - return Package( - dri = driWithPlatform.dri, - functions = scope.functions(driWithPlatform), - properties = scope.properties(driWithPlatform), - classlikes = scope.classlikes(driWithPlatform) - ) - } - - override fun visitClassDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): Classlike = - when (descriptor.kind) { - org.jetbrains.kotlin.descriptors.ClassKind.ENUM_CLASS -> enumDescriptor(descriptor, parent) - else -> classDescriptor(descriptor, parent) - } - - fun enumDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): Enum { - val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo() - val scope = descriptor.unsubstitutedMemberScope - val descriptorData = descriptor.takeUnless { it.isExpect }?.resolveClassDescriptionData() - - return Enum( - dri = driWithPlatform.dri, - name = descriptor.name.asString(), - entries = scope.classlikes(driWithPlatform).filter { it.kind == KotlinClassKindTypes.ENUM_ENTRY }.map { - EnumEntry( - it - ) - }, - constructors = descriptor.constructors.map { visitConstructorDescriptor(it, driWithPlatform) }, - functions = scope.functions(driWithPlatform), - properties = scope.properties(driWithPlatform), - classlikes = scope.classlikes(driWithPlatform), - expected = descriptor.takeIf { it.isExpect }?.resolveClassDescriptionData(), - actual = listOfNotNull(descriptorData), - extra = mutableSetOf(), // TODO Implement following method to return proper results getXMLDRIs(descriptor, descriptorData).toMutableSet() - visibility = mapOf(platformData to descriptor.visibility) - ) - } - - fun classDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): Class { - val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo() - val scope = descriptor.unsubstitutedMemberScope - val descriptorData = descriptor.takeUnless { it.isExpect }?.resolveClassDescriptionData() - val expected = descriptor.takeIf { it.isExpect }?.resolveClassDescriptionData() - val actual = listOfNotNull(descriptorData) - return Class( - dri = driWithPlatform.dri, - name = descriptor.name.asString(), - kind = KotlinClassKindTypes.valueOf(descriptor.kind.toString()), - constructors = descriptor.constructors.map { - visitConstructorDescriptor( - it, - if (it.isPrimary) - DRIWithPlatformInfo( - driWithPlatform.dri, - expected?.info.filterTagWrappers(listOf(Constructor::class)), - actual.filterTagWrappers(listOf(Constructor::class)) - ) - else - DRIWithPlatformInfo(driWithPlatform.dri, null, emptyList()) - ) - }, - functions = scope.functions(driWithPlatform), - properties = scope.properties(driWithPlatform), - classlikes = scope.classlikes(driWithPlatform), - expected = expected, - actual = actual, - extra = mutableSetOf(), // TODO Implement following method to return proper results getXMLDRIs(descriptor, descriptorData).toMutableSet() - visibility = mapOf(platformData to descriptor.visibility) - ) - } - - override fun visitPropertyDescriptor(descriptor: PropertyDescriptor, parent: DRIWithPlatformInfo): Property { - val expected = descriptor.takeIf { it.isExpect }?.resolveDescriptorData() - val actual = listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData()) - val dri = parent.dri.copy(callable = Callable.from(descriptor)) - - return Property( - dri = dri, - name = descriptor.name.asString(), - receiver = descriptor.extensionReceiverParameter?.let { - visitReceiverParameterDescriptor( - it, - DRIWithPlatformInfo( - dri, - expected?.filterTagWrappers(listOf(Receiver::class)), - actual.filterTagWrappers(listOf(Receiver::class)) - ) - ) - }, - expected = expected, - actual = actual, - accessors = descriptor.accessors.map { visitPropertyAccessorDescriptor(it, descriptor, dri) }, - visibility = mapOf(platformData to descriptor.visibility) - ) - } - - override fun visitFunctionDescriptor(descriptor: FunctionDescriptor, parent: DRIWithPlatformInfo): Function { - val expected = descriptor.takeIf { it.isExpect }?.resolveDescriptorData() - val actual = listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData()) - val dri = parent.dri.copy(callable = Callable.from(descriptor)) - - return Function( - dri = dri, - name = descriptor.name.asString(), - returnType = descriptor.returnType?.let { KotlinTypeWrapper(it) }, - isConstructor = false, - receiver = descriptor.extensionReceiverParameter?.let { - visitReceiverParameterDescriptor( - it, - DRIWithPlatformInfo( - dri, - expected?.filterTagWrappers(listOf(Receiver::class)), - actual.filterTagWrappers(listOf(Receiver::class)) - ) - ) - }, - parameters = descriptor.valueParameters.mapIndexed { index, desc -> - parameter( - index, desc, - DRIWithPlatformInfo( - dri, - expected.filterTagWrappers(listOf(Param::class), desc.name.asString()), - actual.filterTagWrappers(listOf(Param::class), desc.name.asString()) - ) - ) - }, - expected = expected, - actual = actual, - visibility = mapOf(platformData to descriptor.visibility) - ) - } - - override fun visitConstructorDescriptor(descriptor: ConstructorDescriptor, parent: DRIWithPlatformInfo): Function { - val dri = parent.dri.copy(callable = Callable.from(descriptor)) - return Function( - dri = dri, - name = "", - returnType = KotlinTypeWrapper(descriptor.returnType), - isConstructor = true, - receiver = null, - parameters = descriptor.valueParameters.mapIndexed { index, desc -> - parameter( - index, desc, - DRIWithPlatformInfo( - dri, - parent.expected.filterTagWrappers(listOf(Param::class)), - parent.actual.filterTagWrappers(listOf(Param::class)) - ) - ) - }, - expected = parent.expected ?: descriptor.takeIf { it.isExpect }?.resolveDescriptorData(), - actual = parent.actual, - visibility = mapOf(platformData to descriptor.visibility) - ) - } - - override fun visitReceiverParameterDescriptor( - descriptor: ReceiverParameterDescriptor, - parent: DRIWithPlatformInfo - ) = Parameter( - dri = parent.dri.copy(target = 0), - name = null, - type = KotlinTypeWrapper(descriptor.type), - expected = parent.expected, - actual = parent.actual - ) - - open fun visitPropertyAccessorDescriptor( - descriptor: PropertyAccessorDescriptor, - propertyDescriptor: PropertyDescriptor, - parent: DRI - ): Function { - val dri = parent.copy(callable = Callable.from(descriptor)) - val isGetter = descriptor is PropertyGetterDescriptor - - fun PropertyDescriptor.asParameter(parent: DRI) = - Parameter( - parent.copy(target = 1), - this.name.asString(), - KotlinTypeWrapper(this.type), - descriptor.takeIf { it.isExpect }?.resolveDescriptorData(), - listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData()) - ) - - val name = run { - val modifier = if (isGetter) "get" else "set" - val rawName = propertyDescriptor.name.asString() - "$modifier${rawName[0].toUpperCase()}${rawName.drop(1)}" - } - - descriptor.visibility - val parameters = - if (isGetter) { - emptyList() - } else { - listOf(propertyDescriptor.asParameter(dri)) - } - - return Function( - dri, - name, - descriptor.returnType?.let { KotlinTypeWrapper(it) }, - false, - null, - parameters, - descriptor.takeIf { it.isExpect }?.resolveDescriptorData(), - listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData()), - visibility = mapOf(platformData to descriptor.visibility) - ) - } - - private fun parameter(index: Int, descriptor: ValueParameterDescriptor, parent: DRIWithPlatformInfo) = - Parameter( - dri = parent.dri.copy(target = index + 1), - name = descriptor.name.asString(), - type = KotlinTypeWrapper(descriptor.type), - expected = parent.expected, - actual = parent.actual - ) - - private fun MemberScope.functions(parent: DRIWithPlatformInfo): List = - getContributedDescriptors(DescriptorKindFilter.FUNCTIONS) { true } - .filterIsInstance() - .map { visitFunctionDescriptor(it, parent) } - - private fun MemberScope.properties(parent: DRIWithPlatformInfo): List = - getContributedDescriptors(DescriptorKindFilter.VALUES) { true } - .filterIsInstance() - .map { visitPropertyDescriptor(it, parent) } - - private fun MemberScope.classlikes(parent: DRIWithPlatformInfo): List = - getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS) { true } - .filterIsInstance() - .map { visitClassDescriptor(it, parent) } - - private fun DeclarationDescriptor.resolveDescriptorData(): PlatformInfo { - val doc = findKDoc() - val parser: MarkdownParser = MarkdownParser(resolutionFacade, this) - val docHeader = parser.parseFromKDocTag(doc) - - return BasePlatformInfo(docHeader, listOf(platformData)) - } - - private fun ClassDescriptor.resolveClassDescriptionData(): ClassPlatformInfo { - return ClassPlatformInfo(resolveDescriptorData(), - (getSuperInterfaces() + getAllSuperclassesWithoutAny()).map { DRI.from(it) }) - } - - private fun PlatformInfo?.filterTagWrappers( - types: List>, - name: String? = null - ): PlatformInfo? { - if (this == null) - return null - return BasePlatformInfo( - DocumentationNode( - this.documentationNode.children.filter { it::class in types && (it as? NamedTagWrapper)?.name == name } - ), - this.platformData - ) - } - - private fun List.filterTagWrappers( - types: List>, - name: String? = null - ): List = - this.map { it.filterTagWrappers(types, name)!! } -} - -data class XMLMega(val key: String, val dri: DRI) : Extra - -enum class KotlinClassKindTypes : ClassKind { - CLASS, - INTERFACE, - ENUM_CLASS, - ENUM_ENTRY, - ANNOTATION_CLASS, - OBJECT; -} - -class KotlinTypeWrapper(private val kotlinType: KotlinType) : TypeWrapper { - private val declarationDescriptor = kotlinType.constructor.declarationDescriptor - private val fqNameSafe = declarationDescriptor?.fqNameSafe - override val constructorFqName = fqNameSafe?.asString() - override val constructorNamePathSegments: List = - fqNameSafe?.pathSegments()?.map { it.asString() } ?: emptyList() - override val arguments: List by lazy { - kotlinType.arguments.map { - KotlinTypeWrapper( - it.type - ) - } - } - override val dri: DRI? by lazy { declarationDescriptor?.let { DRI.from(it) } } -} \ No newline at end of file diff --git a/core/src/main/kotlin/transformers/descriptors/DescriptorToDocumentationTranslator.kt b/core/src/main/kotlin/transformers/descriptors/DescriptorToDocumentationTranslator.kt index 61d636d6..5074833e 100644 --- a/core/src/main/kotlin/transformers/descriptors/DescriptorToDocumentationTranslator.kt +++ b/core/src/main/kotlin/transformers/descriptors/DescriptorToDocumentationTranslator.kt @@ -9,7 +9,6 @@ interface DescriptorToDocumentationTranslator { fun invoke( moduleName: String, packageFragments: Iterable, - platformData: PlatformData, - context: DokkaContext + platformData: PlatformData ): Module } \ No newline at end of file diff --git a/core/src/main/kotlin/transformers/psi/DefaultPsiToDocumentationTranslator.kt b/core/src/main/kotlin/transformers/psi/DefaultPsiToDocumentationTranslator.kt index 144f319f..055df0b2 100644 --- a/core/src/main/kotlin/transformers/psi/DefaultPsiToDocumentationTranslator.kt +++ b/core/src/main/kotlin/transformers/psi/DefaultPsiToDocumentationTranslator.kt @@ -141,75 +141,3 @@ object DefaultPsiToDocumentationTranslator : PsiToDocumentationTranslator { } } } - -enum class JavaClassKindTypes : ClassKind { - CLASS, - INTERFACE, - ENUM_CLASS, - ENUM_ENTRY, - ANNOTATION_CLASS; -} - -class JavaTypeWrapper : TypeWrapper { - - override val constructorFqName: String? - override val constructorNamePathSegments: List - override val arguments: List - override val dri: DRI? - val isPrimitive: Boolean - - constructor( - constructorNamePathSegments: List, - arguments: List, - dri: DRI?, - isPrimitiveType: Boolean - ) { - this.constructorFqName = constructorNamePathSegments.joinToString(".") - this.constructorNamePathSegments = constructorNamePathSegments - this.arguments = arguments - this.dri = dri - this.isPrimitive = isPrimitiveType - } - - constructor(type: PsiType) { - if (type is PsiClassReferenceType) { - val resolved = type.resolve() - constructorFqName = resolved?.qualifiedName - constructorNamePathSegments = resolved?.qualifiedName?.split('.') ?: emptyList() - arguments = type.parameters.mapNotNull { - if (it is PsiClassReferenceType) JavaTypeWrapper(it) else null - } - dri = fromPsi(type) - this.isPrimitive = false - } else if (type is PsiEllipsisType) { - constructorFqName = type.canonicalText - constructorNamePathSegments = listOf(type.canonicalText) // TODO - arguments = emptyList() - dri = DRI("java.lang", "Object") // TODO - this.isPrimitive = false - } else if (type is PsiArrayType) { - constructorFqName = type.canonicalText - constructorNamePathSegments = listOf(type.canonicalText) - arguments = emptyList() - dri = (type as? PsiClassReferenceType)?.let { fromPsi(it) } // TODO - this.isPrimitive = false - } else { - type as PsiPrimitiveType - constructorFqName = type.name - constructorNamePathSegments = type.name.split('.') - arguments = emptyList() - dri = null - this.isPrimitive = true - } - } - - private fun fromPsi(type: PsiClassReferenceType): DRI { - val className = type.className - val pkg = type.canonicalText.removeSuffix(className).removeSuffix(".") - return DRI(packageName = pkg, classNames = className) - } - - override fun toString(): String { - return constructorFqName.orEmpty() - } -} \ No newline at end of file diff --git a/plugins/base/build.gradle.kts b/plugins/base/build.gradle.kts index 386a3288..ba0b5753 100644 --- a/plugins/base/build.gradle.kts +++ b/plugins/base/build.gradle.kts @@ -5,4 +5,5 @@ publishing { from(components["java"]) } } -} \ No newline at end of file +} + diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt index e82a59bf..5c579e54 100644 --- a/plugins/base/src/main/kotlin/DokkaBase.kt +++ b/plugins/base/src/main/kotlin/DokkaBase.kt @@ -1,4 +1,11 @@ +package org.jetbrains.dokka.base + +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.base.transformers.descriptors.DefaultDescriptorToDocumentationTranslator import org.jetbrains.dokka.plugability.DokkaPlugin class DokkaBase: DokkaPlugin() { + val defaultDescriptorToDocumentationTranslator by extending { + CoreExtensions.descriptorToDocumentationTranslator providing ::DefaultDescriptorToDocumentationTranslator + } } \ No newline at end of file diff --git a/plugins/base/src/main/kotlin/transformers/descriptors/DefaultDescriptorToDocumentationTranslator.kt b/plugins/base/src/main/kotlin/transformers/descriptors/DefaultDescriptorToDocumentationTranslator.kt new file mode 100644 index 00000000..1358cef1 --- /dev/null +++ b/plugins/base/src/main/kotlin/transformers/descriptors/DefaultDescriptorToDocumentationTranslator.kt @@ -0,0 +1,338 @@ +package org.jetbrains.dokka.base.transformers.descriptors + +import org.jetbrains.dokka.analysis.DokkaResolutionFacade +import org.jetbrains.dokka.links.Callable +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.withClass +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.ClassKind +import org.jetbrains.dokka.model.Enum +import org.jetbrains.dokka.model.Function +import org.jetbrains.dokka.model.Property +import org.jetbrains.dokka.model.doc.* +import org.jetbrains.dokka.pages.PlatformData +import org.jetbrains.dokka.parsers.MarkdownParser +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.transformers.descriptors.DescriptorToDocumentationTranslator +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBodies +import org.jetbrains.kotlin.idea.kdoc.findKDoc +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny +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.types.KotlinType +import kotlin.reflect.KClass + +class DefaultDescriptorToDocumentationTranslator( + private val context: DokkaContext +) : DescriptorToDocumentationTranslator { + override fun invoke( + moduleName: String, + packageFragments: Iterable, + platformData: PlatformData + ) = DokkaDescriptorVisitor(platformData, context.platforms[platformData]?.facade!!).run { + packageFragments.map { + visitPackageFragmentDescriptor( + it, + DRIWithPlatformInfo(DRI.topLevel, null, emptyList()) + ) + } + }.let { Module(moduleName, it) } + +} + + +data class DRIWithPlatformInfo( + val dri: DRI, + val expected: PlatformInfo?, + val actual: List +) + +fun DRI.withEmptyInfo() = DRIWithPlatformInfo(this, null, emptyList()) + +open class DokkaDescriptorVisitor( // TODO: close this class and make it private together with DRIWithPlatformInfo + private val platformData: PlatformData, + private val resolutionFacade: DokkaResolutionFacade +) : DeclarationDescriptorVisitorEmptyBodies() { + override fun visitDeclarationDescriptor(descriptor: DeclarationDescriptor, parent: DRIWithPlatformInfo): Nothing { + throw IllegalStateException("${javaClass.simpleName} should never enter ${descriptor.javaClass.simpleName}") + } + + override fun visitPackageFragmentDescriptor( + descriptor: PackageFragmentDescriptor, + parent: DRIWithPlatformInfo + ): Package { + val driWithPlatform = DRI(packageName = descriptor.fqName.asString()).withEmptyInfo() + val scope = descriptor.getMemberScope() + + return Package( + dri = driWithPlatform.dri, + functions = scope.functions(driWithPlatform), + properties = scope.properties(driWithPlatform), + classlikes = scope.classlikes(driWithPlatform) + ) + } + + override fun visitClassDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): Classlike = + when (descriptor.kind) { + org.jetbrains.kotlin.descriptors.ClassKind.ENUM_CLASS -> enumDescriptor(descriptor, parent) + else -> classDescriptor(descriptor, parent) + } + + fun enumDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): Enum { + val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo() + val scope = descriptor.unsubstitutedMemberScope + val descriptorData = descriptor.takeUnless { it.isExpect }?.resolveClassDescriptionData() + + return Enum( + dri = driWithPlatform.dri, + name = descriptor.name.asString(), + entries = scope.classlikes(driWithPlatform).filter { it.kind == KotlinClassKindTypes.ENUM_ENTRY }.map { + EnumEntry( + it + ) + }, + constructors = descriptor.constructors.map { visitConstructorDescriptor(it, driWithPlatform) }, + functions = scope.functions(driWithPlatform), + properties = scope.properties(driWithPlatform), + classlikes = scope.classlikes(driWithPlatform), + expected = descriptor.takeIf { it.isExpect }?.resolveClassDescriptionData(), + actual = listOfNotNull(descriptorData), + extra = mutableSetOf(), // TODO Implement following method to return proper results getXMLDRIs(descriptor, descriptorData).toMutableSet() + visibility = mapOf(platformData to descriptor.visibility) + ) + } + + fun classDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): Class { + val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo() + val scope = descriptor.unsubstitutedMemberScope + val descriptorData = descriptor.takeUnless { it.isExpect }?.resolveClassDescriptionData() + val expected = descriptor.takeIf { it.isExpect }?.resolveClassDescriptionData() + val actual = listOfNotNull(descriptorData) + return Class( + dri = driWithPlatform.dri, + name = descriptor.name.asString(), + kind = KotlinClassKindTypes.valueOf(descriptor.kind.toString()), + constructors = descriptor.constructors.map { + visitConstructorDescriptor( + it, + if (it.isPrimary) + DRIWithPlatformInfo( + driWithPlatform.dri, + expected?.info.filterTagWrappers(listOf(Constructor::class)), + actual.filterTagWrappers(listOf(Constructor::class)) + ) + else + DRIWithPlatformInfo(driWithPlatform.dri, null, emptyList()) + ) + }, + functions = scope.functions(driWithPlatform), + properties = scope.properties(driWithPlatform), + classlikes = scope.classlikes(driWithPlatform), + expected = expected, + actual = actual, + extra = mutableSetOf(), // TODO Implement following method to return proper results getXMLDRIs(descriptor, descriptorData).toMutableSet() + visibility = mapOf(platformData to descriptor.visibility) + ) + } + + override fun visitPropertyDescriptor(descriptor: PropertyDescriptor, parent: DRIWithPlatformInfo): Property { + val expected = descriptor.takeIf { it.isExpect }?.resolveDescriptorData() + val actual = listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData()) + val dri = parent.dri.copy(callable = Callable.from(descriptor)) + + return Property( + dri = dri, + name = descriptor.name.asString(), + receiver = descriptor.extensionReceiverParameter?.let { + visitReceiverParameterDescriptor( + it, + DRIWithPlatformInfo( + dri, + expected?.filterTagWrappers(listOf(Receiver::class)), + actual.filterTagWrappers(listOf(Receiver::class)) + ) + ) + }, + expected = expected, + actual = actual, + accessors = descriptor.accessors.map { visitPropertyAccessorDescriptor(it, descriptor, dri) }, + visibility = mapOf(platformData to descriptor.visibility) + ) + } + + override fun visitFunctionDescriptor(descriptor: FunctionDescriptor, parent: DRIWithPlatformInfo): Function { + val expected = descriptor.takeIf { it.isExpect }?.resolveDescriptorData() + val actual = listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData()) + val dri = parent.dri.copy(callable = Callable.from(descriptor)) + + return Function( + dri = dri, + name = descriptor.name.asString(), + returnType = descriptor.returnType?.let { KotlinTypeWrapper(it) }, + isConstructor = false, + receiver = descriptor.extensionReceiverParameter?.let { + visitReceiverParameterDescriptor( + it, + DRIWithPlatformInfo( + dri, + expected?.filterTagWrappers(listOf(Receiver::class)), + actual.filterTagWrappers(listOf(Receiver::class)) + ) + ) + }, + parameters = descriptor.valueParameters.mapIndexed { index, desc -> + parameter( + index, desc, + DRIWithPlatformInfo( + dri, + expected.filterTagWrappers(listOf(Param::class), desc.name.asString()), + actual.filterTagWrappers(listOf(Param::class), desc.name.asString()) + ) + ) + }, + expected = expected, + actual = actual, + visibility = mapOf(platformData to descriptor.visibility) + ) + } + + override fun visitConstructorDescriptor(descriptor: ConstructorDescriptor, parent: DRIWithPlatformInfo): Function { + val dri = parent.dri.copy(callable = Callable.from(descriptor)) + return Function( + dri = dri, + name = "", + returnType = KotlinTypeWrapper(descriptor.returnType), + isConstructor = true, + receiver = null, + parameters = descriptor.valueParameters.mapIndexed { index, desc -> + parameter( + index, desc, + DRIWithPlatformInfo( + dri, + parent.expected.filterTagWrappers(listOf(Param::class)), + parent.actual.filterTagWrappers(listOf(Param::class)) + ) + ) + }, + expected = parent.expected ?: descriptor.takeIf { it.isExpect }?.resolveDescriptorData(), + actual = parent.actual, + visibility = mapOf(platformData to descriptor.visibility) + ) + } + + override fun visitReceiverParameterDescriptor( + descriptor: ReceiverParameterDescriptor, + parent: DRIWithPlatformInfo + ) = Parameter( + dri = parent.dri.copy(target = 0), + name = null, + type = KotlinTypeWrapper(descriptor.type), + expected = parent.expected, + actual = parent.actual + ) + + open fun visitPropertyAccessorDescriptor( + descriptor: PropertyAccessorDescriptor, + propertyDescriptor: PropertyDescriptor, + parent: DRI + ): Function { + val dri = parent.copy(callable = Callable.from(descriptor)) + val isGetter = descriptor is PropertyGetterDescriptor + + fun PropertyDescriptor.asParameter(parent: DRI) = + Parameter( + parent.copy(target = 1), + this.name.asString(), + KotlinTypeWrapper(this.type), + descriptor.takeIf { it.isExpect }?.resolveDescriptorData(), + listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData()) + ) + + val name = run { + val modifier = if (isGetter) "get" else "set" + val rawName = propertyDescriptor.name.asString() + "$modifier${rawName[0].toUpperCase()}${rawName.drop(1)}" + } + + descriptor.visibility + val parameters = + if (isGetter) { + emptyList() + } else { + listOf(propertyDescriptor.asParameter(dri)) + } + + return Function( + dri, + name, + descriptor.returnType?.let { KotlinTypeWrapper(it) }, + false, + null, + parameters, + descriptor.takeIf { it.isExpect }?.resolveDescriptorData(), + listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData()), + visibility = mapOf(platformData to descriptor.visibility) + ) + } + + private fun parameter(index: Int, descriptor: ValueParameterDescriptor, parent: DRIWithPlatformInfo) = + Parameter( + dri = parent.dri.copy(target = index + 1), + name = descriptor.name.asString(), + type = KotlinTypeWrapper(descriptor.type), + expected = parent.expected, + actual = parent.actual + ) + + private fun MemberScope.functions(parent: DRIWithPlatformInfo): List = + getContributedDescriptors(DescriptorKindFilter.FUNCTIONS) { true } + .filterIsInstance() + .map { visitFunctionDescriptor(it, parent) } + + private fun MemberScope.properties(parent: DRIWithPlatformInfo): List = + getContributedDescriptors(DescriptorKindFilter.VALUES) { true } + .filterIsInstance() + .map { visitPropertyDescriptor(it, parent) } + + private fun MemberScope.classlikes(parent: DRIWithPlatformInfo): List = + getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS) { true } + .filterIsInstance() + .map { visitClassDescriptor(it, parent) } + + private fun DeclarationDescriptor.resolveDescriptorData(): PlatformInfo { + val doc = findKDoc() + val parser = MarkdownParser(resolutionFacade, this) + val docHeader = parser.parseFromKDocTag(doc) + + return BasePlatformInfo(docHeader, listOf(platformData)) + } + + private fun ClassDescriptor.resolveClassDescriptionData(): ClassPlatformInfo { + return ClassPlatformInfo(resolveDescriptorData(), + (getSuperInterfaces() + getAllSuperclassesWithoutAny()).map { DRI.from(it) }) + } + + private fun PlatformInfo?.filterTagWrappers( + types: List>, + name: String? = null + ): PlatformInfo? { + if (this == null) + return null + return BasePlatformInfo( + DocumentationNode( + this.documentationNode.children.filter { it::class in types && (it as? NamedTagWrapper)?.name == name } + ), + this.platformData + ) + } + + private fun List.filterTagWrappers( + types: List>, + name: String? = null + ): List = + this.map { it.filterTagWrappers(types, name)!! } +} + diff --git a/plugins/kotlin-as-java/build.gradle.kts b/plugins/kotlin-as-java/build.gradle.kts index 3b281d53..32f9c931 100644 --- a/plugins/kotlin-as-java/build.gradle.kts +++ b/plugins/kotlin-as-java/build.gradle.kts @@ -5,4 +5,8 @@ publishing { from(components["java"]) } } +} + +dependencies { + implementation(project(":plugins:base")) } \ No newline at end of file diff --git a/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaDescriptorToDocumentationTranslator.kt b/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaDescriptorToDocumentationTranslator.kt index 9c4ee9aa..1edf4aa1 100644 --- a/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaDescriptorToDocumentationTranslator.kt +++ b/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaDescriptorToDocumentationTranslator.kt @@ -1,6 +1,9 @@ package org.jetbrains.dokka.kotlinAsJava import org.jetbrains.dokka.analysis.DokkaResolutionFacade +import org.jetbrains.dokka.base.transformers.descriptors.DRIWithPlatformInfo +import org.jetbrains.dokka.base.transformers.descriptors.DokkaDescriptorVisitor +import org.jetbrains.dokka.base.transformers.descriptors.withEmptyInfo import org.jetbrains.dokka.links.Callable import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.links.withClass @@ -8,18 +11,16 @@ import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.Function import org.jetbrains.dokka.pages.PlatformData import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.transformers.descriptors.DRIWithPlatformInfo import org.jetbrains.dokka.transformers.descriptors.DescriptorToDocumentationTranslator -import org.jetbrains.dokka.transformers.descriptors.DokkaDescriptorVisitor -import org.jetbrains.dokka.transformers.descriptors.withEmptyInfo import org.jetbrains.kotlin.descriptors.* -object KotlinAsJavaDescriptorToDocumentationTranslator : DescriptorToDocumentationTranslator { +class KotlinAsJavaDescriptorToDocumentationTranslator( + private val context: DokkaContext +) : DescriptorToDocumentationTranslator { override fun invoke( moduleName: String, packageFragments: Iterable, - platformData: PlatformData, - context: DokkaContext + platformData: PlatformData ): Module = KotlinAsJavaDokkaDescriptorVisitor(platformData, context.platforms[platformData]?.facade!!).run { packageFragments.map { visitPackageFragmentDescriptor(it, DRI.topLevel.withEmptyInfo()) } diff --git a/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPageBuilder.kt b/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPageBuilder.kt index 8f026477..6fe10ce1 100644 --- a/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPageBuilder.kt +++ b/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPageBuilder.kt @@ -9,7 +9,6 @@ import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.Enum import org.jetbrains.dokka.model.Function import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.transformers.descriptors.KotlinClassKindTypes import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi diff --git a/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPageContentBuilder.kt b/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPageContentBuilder.kt index 05896d11..a5d0bd33 100644 --- a/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPageContentBuilder.kt +++ b/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPageContentBuilder.kt @@ -2,8 +2,8 @@ package org.jetbrains.dokka.kotlinAsJava import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.Function +import org.jetbrains.dokka.model.JavaTypeWrapper import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.transformers.psi.JavaTypeWrapper import org.jetbrains.dokka.utilities.DokkaLogger class KotlinAsJavaPageContentBuilder( diff --git a/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPlugin.kt b/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPlugin.kt index 345dc9be..e99e0843 100644 --- a/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPlugin.kt +++ b/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPlugin.kt @@ -12,8 +12,12 @@ import org.jetbrains.dokka.transformers.documentation.DocumentationToPageTransla import org.jetbrains.kotlin.descriptors.DeclarationDescriptor class KotlinAsJavaPlugin : DokkaPlugin() { - val kotlinAsJavaDescriptorToDocumentableTranslator by extending { CoreExtensions.descriptorToDocumentationTranslator with KotlinAsJavaDescriptorToDocumentationTranslator } - val kotlinAsJavaDocumentableToPageTranslator by extending { CoreExtensions.documentationToPageTranslator with KotlinAsJavaDocumentationToPageTranslator } + val kotlinAsJavaDescriptorToDocumentableTranslator by extending { + CoreExtensions.descriptorToDocumentationTranslator providing ::KotlinAsJavaDescriptorToDocumentationTranslator + } + val kotlinAsJavaDocumentableToPageTranslator by extending { + CoreExtensions.documentationToPageTranslator with KotlinAsJavaDocumentationToPageTranslator + } } object DescriptorCache { diff --git a/plugins/kotlin-as-java/src/main/kotlin/KotlinToJVMResolver.kt b/plugins/kotlin-as-java/src/main/kotlin/KotlinToJVMResolver.kt index 87a173f3..7b0495e9 100644 --- a/plugins/kotlin-as-java/src/main/kotlin/KotlinToJVMResolver.kt +++ b/plugins/kotlin-as-java/src/main/kotlin/KotlinToJVMResolver.kt @@ -5,7 +5,6 @@ import org.jetbrains.dokka.links.* import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.Function import org.jetbrains.dokka.model.Enum -import org.jetbrains.dokka.transformers.psi.JavaTypeWrapper import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor -- cgit From f625cef495d625d81ee22e950083f57cc4fab875 Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Mon, 17 Feb 2020 09:32:35 +0100 Subject: Moves PsiToDocumentablesTranslator to the base plugin --- core/build.gradle.kts | 1 - core/src/main/kotlin/CoreExtensions.kt | 5 +- .../main/kotlin/plugability/DefaultExtensions.kt | 10 - core/src/main/kotlin/renderers/DefaultRenderer.kt | 124 ------------ .../src/main/kotlin/renderers/html/HtmlRenderer.kt | 217 --------------------- .../main/kotlin/renderers/html/NavigationPage.kt | 50 ----- .../kotlin/renderers/html/htmlPreprocessors.kt | 68 ------- .../psi/DefaultPsiToDocumentationTranslator.kt | 143 -------------- plugins/base/build.gradle.kts | 4 + plugins/base/src/main/kotlin/DokkaBase.kt | 10 + .../src/main/kotlin/renderers/DefaultRenderer.kt | 127 ++++++++++++ .../src/main/kotlin/renderers/html/HtmlRenderer.kt | 216 ++++++++++++++++++++ .../main/kotlin/renderers/html/NavigationPage.kt | 50 +++++ .../kotlin/renderers/html/htmlPreprocessors.kt | 68 +++++++ .../psi/DefaultPsiToDocumentationTranslator.kt | 145 ++++++++++++++ 15 files changed, 623 insertions(+), 615 deletions(-) delete mode 100644 core/src/main/kotlin/renderers/DefaultRenderer.kt delete mode 100644 core/src/main/kotlin/renderers/html/HtmlRenderer.kt delete mode 100644 core/src/main/kotlin/renderers/html/NavigationPage.kt delete mode 100644 core/src/main/kotlin/renderers/html/htmlPreprocessors.kt delete mode 100644 core/src/main/kotlin/transformers/psi/DefaultPsiToDocumentationTranslator.kt create mode 100644 plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt create mode 100644 plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt create mode 100644 plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt create mode 100644 plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt create mode 100644 plugins/base/src/main/kotlin/transformers/psi/DefaultPsiToDocumentationTranslator.kt (limited to 'plugins/base/build.gradle.kts') diff --git a/core/build.gradle.kts b/core/build.gradle.kts index fbe9d563..365cf7e8 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -10,7 +10,6 @@ dependencies { val kotlin_version: String by project api("org.jetbrains.kotlin:kotlin-compiler:$kotlin_version") - implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.10") implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") implementation("org.jsoup:jsoup:1.12.1") diff --git a/core/src/main/kotlin/CoreExtensions.kt b/core/src/main/kotlin/CoreExtensions.kt index 8ae333ee..2d705c1b 100644 --- a/core/src/main/kotlin/CoreExtensions.kt +++ b/core/src/main/kotlin/CoreExtensions.kt @@ -24,12 +24,13 @@ object CoreExtensions { val psiToDocumentationTranslator by coreExtension() val documentableMerger by coreExtension() val documentationTransformer by coreExtension() - val commentsToContentConverter by coreExtension() val documentablesToPageTranslator by coreExtension() val pageTransformer by coreExtension() + val renderer by coreExtension() + + val commentsToContentConverter by coreExtension() val locationProviderFactory by coreExtension() val outputWriter by coreExtension() - val renderer by coreExtension() val pageMergerStrategy by coreExtension() private fun coreExtension() = object { diff --git a/core/src/main/kotlin/plugability/DefaultExtensions.kt b/core/src/main/kotlin/plugability/DefaultExtensions.kt index 7c76afe7..828d1bf1 100644 --- a/core/src/main/kotlin/plugability/DefaultExtensions.kt +++ b/core/src/main/kotlin/plugability/DefaultExtensions.kt @@ -4,20 +4,12 @@ import org.jetbrains.dokka.CoreExtensions import org.jetbrains.dokka.pages.DocTagToContentConverter import org.jetbrains.dokka.renderers.FileWriter import org.jetbrains.dokka.renderers.OutputWriter -import org.jetbrains.dokka.renderers.html.HtmlRenderer import org.jetbrains.dokka.resolvers.DefaultLocationProviderFactory import org.jetbrains.dokka.transformers.pages.DefaultPageMergerStrategy -import org.jetbrains.dokka.transformers.psi.DefaultPsiToDocumentationTranslator import org.jetbrains.dokka.transformers.pages.DefaultPageNodeMerger internal object DefaultExtensions { - private val renderer: LazyEvaluated = LazyEvaluated.fromRecipe { - HtmlRenderer( - it.single(CoreExtensions.outputWriter), - it - ) - } private val converter: LazyEvaluated = LazyEvaluated.fromRecipe { DocTagToContentConverter(it) } private val providerFactory: LazyEvaluated = LazyEvaluated.fromRecipe { DefaultLocationProviderFactory(it) } private val outputWriter: LazyEvaluated = LazyEvaluated.fromRecipe { FileWriter(it) } @@ -25,10 +17,8 @@ internal object DefaultExtensions { @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST") internal fun > get(point: E, fullContext: DokkaContext): List = when (point) { - CoreExtensions.psiToDocumentationTranslator -> DefaultPsiToDocumentationTranslator CoreExtensions.commentsToContentConverter -> converter.get(fullContext) CoreExtensions.pageTransformer -> DefaultPageNodeMerger(fullContext) - CoreExtensions.renderer -> renderer.get(fullContext) CoreExtensions.locationProviderFactory -> providerFactory.get(fullContext) CoreExtensions.outputWriter -> outputWriter.get(fullContext) CoreExtensions.pageMergerStrategy -> DefaultPageMergerStrategy(fullContext.logger) diff --git a/core/src/main/kotlin/renderers/DefaultRenderer.kt b/core/src/main/kotlin/renderers/DefaultRenderer.kt deleted file mode 100644 index d09d9ded..00000000 --- a/core/src/main/kotlin/renderers/DefaultRenderer.kt +++ /dev/null @@ -1,124 +0,0 @@ -package org.jetbrains.dokka.renderers - -import org.jetbrains.dokka.CoreExtensions -import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.resolvers.LocationProvider -import org.jetbrains.dokka.transformers.pages.PageNodeTransformer - -abstract class DefaultRenderer( - protected val outputWriter: OutputWriter, - protected val context: DokkaContext -) : Renderer { - - protected lateinit var locationProvider: LocationProvider - private set - - protected open val preprocessors: Iterable = emptyList() - - abstract fun T.buildHeader(level: Int, content: T.() -> Unit) - abstract fun T.buildLink(address: String, content: T.() -> Unit) - abstract fun T.buildList(node: ContentList, pageContext: ContentPage) - abstract fun T.buildNewLine() - abstract fun T.buildResource(node: ContentEmbeddedResource, pageContext: ContentPage) - abstract fun T.buildTable(node: ContentTable, pageContext: ContentPage) - abstract fun T.buildText(textNode: ContentText) - abstract fun T.buildNavigation(page: PageNode) - - abstract fun buildPage(page: ContentPage, content: (T, ContentPage) -> Unit): String - abstract fun buildError(node: ContentNode) - - open fun T.buildGroup(node: ContentGroup, pageContext: ContentPage) { - node.children.forEach { it.build(this, pageContext) } - } - - open fun T.buildLinkText(nodes: List, pageContext: ContentPage) { - nodes.forEach { it.build(this, pageContext) } - } - - open fun T.buildCode(code: List, language: String, pageContext: ContentPage) { - code.forEach { it.build(this, pageContext) } - } - - open fun T.buildHeader(node: ContentHeader, pageContext: ContentPage) { - buildHeader(node.level) { node.children.forEach { it.build(this, pageContext) } } - } - - open fun ContentNode.build(builder: T, pageContext: ContentPage) = - builder.buildContentNode(this, pageContext) - - open fun T.buildContentNode(node: ContentNode, pageContext: ContentPage) { - when (node) { - is ContentText -> buildText(node) - is ContentHeader -> buildHeader(node, pageContext) - is ContentCode -> buildCode(node.children, node.language, pageContext) - is ContentDRILink -> buildLink( - locationProvider.resolve(node.address, node.platforms.toList(), pageContext) - ) { - buildLinkText(node.children, pageContext) - } - is ContentResolvedLink -> buildLink(node.address) { buildLinkText(node.children, pageContext) } - is ContentEmbeddedResource -> buildResource(node, pageContext) - is ContentList -> buildList(node, pageContext) - is ContentTable -> buildTable(node, pageContext) - is ContentGroup -> buildGroup(node, pageContext) - else -> buildError(node) - } - } - - open fun buildPageContent(context: T, page: ContentPage) { - context.buildNavigation(page) - page.content.build(context, page) - } - - open fun renderPage(page: PageNode) { - val path by lazy { locationProvider.resolve(page, skipExtension = true) } - when (page) { - is ContentPage -> outputWriter.write(path, buildPage(page) { c, p -> buildPageContent(c, p) }, ".html") - is RendererSpecificPage -> when (val strategy = page.strategy) { - is RenderingStrategy.Copy -> outputWriter.writeResources(strategy.from, path) - is RenderingStrategy.Write -> outputWriter.write(path, strategy.text, "") - is RenderingStrategy.Callback -> outputWriter.write(path, strategy.instructions(this, page), ".html") - RenderingStrategy.DoNothing -> Unit - } - else -> throw AssertionError( - "Page ${page.name} cannot be rendered by renderer as it is not renderer specific nor contains content" - ) - } - } - - open fun renderPages(root: PageNode) { - renderPage(root) - root.children.forEach { renderPages(it) } - } - - // reimplement this as preprocessor - open fun renderPackageList(root: ContentPage) = - getPackageNamesAndPlatforms(root) - .keys - .joinToString("\n") - .also { outputWriter.write("${root.name}/package-list", it, "") } - - open fun getPackageNamesAndPlatforms(root: PageNode): Map> = - root.children - .map(::getPackageNamesAndPlatforms) - .fold(emptyMap>()) { e, acc -> acc + e } + - if (root is PackagePageNode) { - mapOf(root.name to root.platforms()) - } else { - emptyMap() - } - - override fun render(root: RootPageNode) { - val newRoot = preprocessors.fold(root) { acc, t -> t(acc) } - - locationProvider = - context.single(CoreExtensions.locationProviderFactory).getLocationProvider(newRoot) - - root.children().forEach { renderPackageList(it) } - - renderPages(newRoot) - } -} - -fun ContentPage.platforms() = this.content.platforms.toList() \ No newline at end of file diff --git a/core/src/main/kotlin/renderers/html/HtmlRenderer.kt b/core/src/main/kotlin/renderers/html/HtmlRenderer.kt deleted file mode 100644 index 376ce5c8..00000000 --- a/core/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ /dev/null @@ -1,217 +0,0 @@ -package org.jetbrains.dokka.renderers.html - -import kotlinx.html.* -import kotlinx.html.stream.createHTML -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.Function -import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.renderers.DefaultRenderer -import org.jetbrains.dokka.renderers.OutputWriter -import java.io.File - -open class HtmlRenderer( - outputWriter: OutputWriter, - context: DokkaContext -) : DefaultRenderer(outputWriter, context) { - - private val pageList = mutableListOf() - - override val preprocessors = listOf( - RootCreator, - SearchPageInstaller, - ResourceInstaller, - NavigationPageInstaller, - StyleAndScriptsAppender - ) - - override fun FlowContent.buildList(node: ContentList, pageContext: ContentPage) = - if (node.ordered) ol { - buildListItems(node.children, pageContext) - } - else ul { - buildListItems(node.children, pageContext) - } - - open fun OL.buildListItems(items: List, pageContext: ContentPage) { - items.forEach { - if (it is ContentList) - buildList(it, pageContext) - else - li { it.build(this, pageContext) } - } - } - - open fun UL.buildListItems(items: List, pageContext: ContentPage) { - items.forEach { - if (it is ContentList) - buildList(it, pageContext) - else - li { it.build(this, pageContext) } - } - } - - override fun FlowContent.buildResource( - node: ContentEmbeddedResource, - pageContext: ContentPage - ) { // TODO: extension point there - val imageExtensions = setOf("png", "jpg", "jpeg", "gif", "bmp", "tif", "webp", "svg") - return if (File(node.address).extension.toLowerCase() in imageExtensions) { - //TODO: add imgAttrs parsing - val imgAttrs = node.extras.filterIsInstance().joinAttr() - img(src = node.address, alt = node.altText) - } else { - println("Unrecognized resource type: $node") - } - } - - override fun FlowContent.buildTable(node: ContentTable, pageContext: ContentPage) { - table { - thead { - node.header.forEach { - tr { - it.children.forEach { - th { - it.build(this@table, pageContext) - } - } - } - } - } - tbody { - node.children.forEach { - tr { - it.children.forEach { - td { - it.build(this, pageContext) - } - } - } - } - } - } - } - - override fun FlowContent.buildHeader(level: Int, content: FlowContent.() -> Unit) { - when (level) { - 1 -> h1(block = content) - 2 -> h2(block = content) - 3 -> h3(block = content) - 4 -> h4(block = content) - 5 -> h5(block = content) - else -> h6(block = content) - } - } - - override fun FlowContent.buildNavigation(page: PageNode) = - locationProvider.ancestors(page).asReversed().forEach { node -> - text("/") - if (node.isNavigable) buildLink(node, page) - else text(node.name) - } - - private fun FlowContent.buildLink(to: PageNode, from: PageNode) = - buildLink(locationProvider.resolve(to, from)) { - text(to.name) - } - - fun FlowContent.buildLink( - to: DRI, - platforms: List, - from: PageNode? = null, - block: FlowContent.() -> Unit - ) = buildLink(locationProvider.resolve(to, platforms, from), block) - - override fun buildError(node: ContentNode) { - context.logger.error("Unknown ContentNode type: $node") - } - - override fun FlowContent.buildNewLine() { - br() - } - - override fun FlowContent.buildLink(address: String, content: FlowContent.() -> Unit) = - a(href = address, block = content) - - override fun FlowContent.buildCode(code: List, language: String, pageContext: ContentPage) { - buildNewLine() - code.forEach { - +((it as? ContentText)?.text ?: run { context.logger.error("Cannot cast $it as ContentText!"); "" }) - buildNewLine() - } - } - - override fun renderPage(page: PageNode) { - super.renderPage(page) - if (page is ContentPage) { - pageList.add( - """{ "name": "${page.name}", ${if (page is ClasslikePageNode) "\"class\": \"${page.name}\"," else ""} "location": "${locationProvider.resolve( - page - )}" }""" - ) - } - } - - override fun FlowContent.buildText(textNode: ContentText) { - text(textNode.text) - } - - override fun render(root: RootPageNode) { - super.render(root) - outputWriter.write("scripts/pages", "var pages = [\n${pageList.joinToString(",\n")}\n]", ".js") - } - - private fun PageNode.root(path: String) = locationProvider.resolveRoot(this) + path - - override fun buildPage(page: ContentPage, content: (FlowContent, ContentPage) -> Unit): String = - buildHtml(page, page.embeddedResources) { content(this, page) } - - open fun buildHtml(page: PageNode, resources: List, content: FlowContent.() -> Unit) = - createHTML().html { - head { - title(page.name) - with(resources) { - filter { it.substringBefore('?').substringAfterLast('.') == "css" } - .forEach { link(rel = LinkRel.stylesheet, href = page.root(it)) } - filter { it.substringBefore('?').substringAfterLast('.') == "js" } - .forEach { script(type = ScriptType.textJavaScript, src = page.root(it)) { async = true } } - } - script { unsafe { +"""var pathToRoot = "${locationProvider.resolveRoot(page)}";""" } } - } - body { - div { - id = "navigation" - div { - id = "searchBar" - form(action = page.root("-search.html"), method = FormMethod.get) { - id = "searchForm" - input(type = InputType.search, name = "query") - input(type = InputType.submit) { value = "Search" } - } - } - div { - id = "sideMenu" - } - } - div { - id = "content" - content() - } - } - } -} - -fun List.joinAttr() = joinToString(" ") { it.key + "=" + it.value } - -private fun PageNode.pageKind() = when (this) { - is PackagePageNode -> "package" - is ClasslikePageNode -> "class" - is MemberPageNode -> when (this.documentable) { - is Function -> "function" - else -> "other" - } - else -> "other" -} - -private val PageNode.isNavigable: Boolean - get() = this !is RendererSpecificPage || strategy != RenderingStrategy.DoNothing \ No newline at end of file diff --git a/core/src/main/kotlin/renderers/html/NavigationPage.kt b/core/src/main/kotlin/renderers/html/NavigationPage.kt deleted file mode 100644 index 4a2fb40d..00000000 --- a/core/src/main/kotlin/renderers/html/NavigationPage.kt +++ /dev/null @@ -1,50 +0,0 @@ -package org.jetbrains.dokka.renderers.html - -import kotlinx.html.* -import kotlinx.html.stream.createHTML -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.pages.PageNode -import org.jetbrains.dokka.pages.PlatformData -import org.jetbrains.dokka.pages.RendererSpecificPage -import org.jetbrains.dokka.pages.RenderingStrategy - -class NavigationPage(val root: NavigationNode) : RendererSpecificPage { - override val name = "navigation" - - override val children = emptyList() - - override fun modified(name: String, children: List) = this - - override val strategy = RenderingStrategy { - createHTML().visit(root, "nav-submenu", this) - } - - private fun TagConsumer.visit(node: NavigationNode, navId: String, renderer: HtmlRenderer): R = - with(renderer) { - div("sideMenuPart") { - id = navId - div("overview") { - buildLink(node.dri, node.platforms) { +node.name } - if (node.children.isNotEmpty()) { - span("navButton") { - onClick = """document.getElementById("$navId").classList.toggle("hidden");""" - span("navButtonContent") - } - } - } - node.children.withIndex().forEach { (n, p) -> visit(p, "$navId-$n", renderer) } - } - } -} - -class NavigationNode( - val name: String, - val dri: DRI, - val platforms: List, - val children: List -) - -fun NavigationPage.transform(block: (NavigationNode) -> NavigationNode) = NavigationPage(root.transform(block)) - -fun NavigationNode.transform(block: (NavigationNode) -> NavigationNode) = - run(block).let { NavigationNode(it.name, it.dri, it.platforms, it.children.map(block)) } diff --git a/core/src/main/kotlin/renderers/html/htmlPreprocessors.kt b/core/src/main/kotlin/renderers/html/htmlPreprocessors.kt deleted file mode 100644 index 4ee67448..00000000 --- a/core/src/main/kotlin/renderers/html/htmlPreprocessors.kt +++ /dev/null @@ -1,68 +0,0 @@ -package org.jetbrains.dokka.renderers.html - -import kotlinx.html.h1 -import kotlinx.html.id -import kotlinx.html.table -import kotlinx.html.tbody -import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.renderers.platforms -import org.jetbrains.dokka.transformers.pages.PageNodeTransformer - -object RootCreator : PageNodeTransformer { - override fun invoke(input: RootPageNode) = - RendererSpecificRootPage("", listOf(input), RenderingStrategy.DoNothing) -} - -object SearchPageInstaller : PageNodeTransformer { - override fun invoke(input: RootPageNode) = input.modified(children = input.children + searchPage) - - private val searchPage = RendererSpecificResourcePage( - name = "Search", - children = emptyList(), - strategy = RenderingStrategy { - buildHtml(it, listOf("styles/style.css", "scripts/pages.js")) { - h1 { - id = "searchTitle" - text("Search results for ") - } - table { - tbody { - id = "searchTable" - } - } - } - }) -} - -object NavigationPageInstaller : PageNodeTransformer { - override fun invoke(input: RootPageNode) = input.modified( - children = input.children + NavigationPage( - input.children.filterIsInstance().single().let(::visit) - ) - ) - - private fun visit(page: ContentPage): NavigationNode = NavigationNode( - page.name, - page.dri.first(), - page.platforms(), - page.children.filterIsInstance().map { visit(it) }) -} - -object ResourceInstaller : PageNodeTransformer { - override fun invoke(input: RootPageNode) = input.modified(children = input.children + resourcePages) - - private val resourcePages = listOf("styles", "scripts", "images").map { - RendererSpecificResourcePage(it, emptyList(), RenderingStrategy.Copy("/dokka/$it")) - } -} - -object StyleAndScriptsAppender : PageNodeTransformer { - override fun invoke(input: RootPageNode) = input.transformContentPagesTree { - it.modified( - embeddedResources = it.embeddedResources + listOf( - "styles/style.css", - "scripts/navigationLoader.js" - ) - ) - } -} diff --git a/core/src/main/kotlin/transformers/psi/DefaultPsiToDocumentationTranslator.kt b/core/src/main/kotlin/transformers/psi/DefaultPsiToDocumentationTranslator.kt deleted file mode 100644 index 055df0b2..00000000 --- a/core/src/main/kotlin/transformers/psi/DefaultPsiToDocumentationTranslator.kt +++ /dev/null @@ -1,143 +0,0 @@ -package org.jetbrains.dokka.transformers.psi - -import com.intellij.psi.* -import com.intellij.psi.impl.source.PsiClassReferenceType -import org.jetbrains.dokka.JavadocParser -import org.jetbrains.dokka.links.Callable -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.Function -import org.jetbrains.dokka.pages.PlatformData -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.kotlin.descriptors.Visibilities -import org.jetbrains.kotlin.idea.caches.project.getPlatformModuleInfo -import org.jetbrains.kotlin.platform.TargetPlatform - -object DefaultPsiToDocumentationTranslator : PsiToDocumentationTranslator { - - override fun invoke( - moduleName: String, - psiFiles: List, - platformData: PlatformData, - context: DokkaContext - ): Module { - val docParser = DokkaPsiParser(platformData, context.logger) - return Module(moduleName, - psiFiles.map { psiFile -> - val dri = DRI(packageName = psiFile.packageName) - Package( - dri, - emptyList(), - emptyList(), - psiFile.classes.map { docParser.parseClass(it, dri) } - ) - } - ) - } - - class DokkaPsiParser( - private val platformData: PlatformData, - logger: DokkaLogger - ) { - - 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 - ml.any { it.text == PsiKeyword.PROTECTED } -> Visibilities.PROTECTED - else -> Visibilities.PRIVATE - } - } ?: 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 - } - 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 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 { - 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, - JavaTypeWrapper(psiParameter.type), - null, - getComment(psi) - ) - }, - null, - getComment(psi), - visibility = mapOf(platformData to psi.getVisibility()) - ) - } - - private fun parseField(psi: PsiField, parent: DRI): Property { - val dri = parent.copy( - callable = Callable( - psi.name, - JavaClassReference(psi.containingClass?.name.orEmpty()), - emptyList() - ) - ) - return Property( - dri, - psi.name, - null, - null, - getComment(psi), - accessors = emptyList(), - visibility = mapOf(platformData to psi.getVisibility()) - ) - } - } -} diff --git a/plugins/base/build.gradle.kts b/plugins/base/build.gradle.kts index ba0b5753..b42d7e28 100644 --- a/plugins/base/build.gradle.kts +++ b/plugins/base/build.gradle.kts @@ -7,3 +7,7 @@ publishing { } } +dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.10") +} + diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt index 34c0ffae..bafd30ff 100644 --- a/plugins/base/src/main/kotlin/DokkaBase.kt +++ b/plugins/base/src/main/kotlin/DokkaBase.kt @@ -4,13 +4,19 @@ import org.jetbrains.dokka.CoreExtensions import org.jetbrains.dokka.base.transformers.descriptors.DefaultDescriptorToDocumentationTranslator import org.jetbrains.dokka.base.transformers.documentables.DefaultDocumentableMerger import org.jetbrains.dokka.base.transformers.documentables.DefaultDocumentablesToPageTranslator +import org.jetbrains.dokka.base.transformers.psi.DefaultPsiToDocumentationTranslator import org.jetbrains.dokka.plugability.DokkaPlugin +import org.jetbrains.dokka.renderers.html.HtmlRenderer class DokkaBase: DokkaPlugin() { val descriptorToDocumentationTranslator by extending(isFallback = true) { CoreExtensions.descriptorToDocumentationTranslator providing ::DefaultDescriptorToDocumentationTranslator } + val psiToDocumentationTranslator by extending(isFallback = true) { + CoreExtensions.psiToDocumentationTranslator with DefaultPsiToDocumentationTranslator + } + val documentableMerger by extending(isFallback = true) { CoreExtensions.documentableMerger with DefaultDocumentableMerger } @@ -18,4 +24,8 @@ class DokkaBase: DokkaPlugin() { val documentablesToPageTranslator by extending(isFallback = true) { CoreExtensions.documentablesToPageTranslator with DefaultDocumentablesToPageTranslator } + + val htmlRenderer by extending { + CoreExtensions.renderer providing ::HtmlRenderer + } } \ No newline at end of file diff --git a/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt b/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt new file mode 100644 index 00000000..c6183cf3 --- /dev/null +++ b/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt @@ -0,0 +1,127 @@ +package org.jetbrains.dokka.base.renderers + +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.renderers.OutputWriter +import org.jetbrains.dokka.renderers.Renderer +import org.jetbrains.dokka.resolvers.LocationProvider +import org.jetbrains.dokka.transformers.pages.PageNodeTransformer + +abstract class DefaultRenderer( + protected val context: DokkaContext +) : Renderer { + + protected val outputWriter = context.single(CoreExtensions.outputWriter) + + protected lateinit var locationProvider: LocationProvider + private set + + protected open val preprocessors: Iterable = emptyList() + + abstract fun T.buildHeader(level: Int, content: T.() -> Unit) + abstract fun T.buildLink(address: String, content: T.() -> Unit) + abstract fun T.buildList(node: ContentList, pageContext: ContentPage) + abstract fun T.buildNewLine() + abstract fun T.buildResource(node: ContentEmbeddedResource, pageContext: ContentPage) + abstract fun T.buildTable(node: ContentTable, pageContext: ContentPage) + abstract fun T.buildText(textNode: ContentText) + abstract fun T.buildNavigation(page: PageNode) + + abstract fun buildPage(page: ContentPage, content: (T, ContentPage) -> Unit): String + abstract fun buildError(node: ContentNode) + + open fun T.buildGroup(node: ContentGroup, pageContext: ContentPage) { + node.children.forEach { it.build(this, pageContext) } + } + + open fun T.buildLinkText(nodes: List, pageContext: ContentPage) { + nodes.forEach { it.build(this, pageContext) } + } + + open fun T.buildCode(code: List, language: String, pageContext: ContentPage) { + code.forEach { it.build(this, pageContext) } + } + + open fun T.buildHeader(node: ContentHeader, pageContext: ContentPage) { + buildHeader(node.level) { node.children.forEach { it.build(this, pageContext) } } + } + + open fun ContentNode.build(builder: T, pageContext: ContentPage) = + builder.buildContentNode(this, pageContext) + + open fun T.buildContentNode(node: ContentNode, pageContext: ContentPage) { + when (node) { + is ContentText -> buildText(node) + is ContentHeader -> buildHeader(node, pageContext) + is ContentCode -> buildCode(node.children, node.language, pageContext) + is ContentDRILink -> buildLink( + locationProvider.resolve(node.address, node.platforms.toList(), pageContext) + ) { + buildLinkText(node.children, pageContext) + } + is ContentResolvedLink -> buildLink(node.address) { buildLinkText(node.children, pageContext) } + is ContentEmbeddedResource -> buildResource(node, pageContext) + is ContentList -> buildList(node, pageContext) + is ContentTable -> buildTable(node, pageContext) + is ContentGroup -> buildGroup(node, pageContext) + else -> buildError(node) + } + } + + open fun buildPageContent(context: T, page: ContentPage) { + context.buildNavigation(page) + page.content.build(context, page) + } + + open fun renderPage(page: PageNode) { + val path by lazy { locationProvider.resolve(page, skipExtension = true) } + when (page) { + is ContentPage -> outputWriter.write(path, buildPage(page) { c, p -> buildPageContent(c, p) }, ".html") + is RendererSpecificPage -> when (val strategy = page.strategy) { + is RenderingStrategy.Copy -> outputWriter.writeResources(strategy.from, path) + is RenderingStrategy.Write -> outputWriter.write(path, strategy.text, "") + is RenderingStrategy.Callback -> outputWriter.write(path, strategy.instructions(this, page), ".html") + RenderingStrategy.DoNothing -> Unit + } + else -> throw AssertionError( + "Page ${page.name} cannot be rendered by renderer as it is not renderer specific nor contains content" + ) + } + } + + open fun renderPages(root: PageNode) { + renderPage(root) + root.children.forEach { renderPages(it) } + } + + // reimplement this as preprocessor + open fun renderPackageList(root: ContentPage) = + getPackageNamesAndPlatforms(root) + .keys + .joinToString("\n") + .also { outputWriter.write("${root.name}/package-list", it, "") } + + open fun getPackageNamesAndPlatforms(root: PageNode): Map> = + root.children + .map(::getPackageNamesAndPlatforms) + .fold(emptyMap>()) { e, acc -> acc + e } + + if (root is PackagePageNode) { + mapOf(root.name to root.platforms()) + } else { + emptyMap() + } + + override fun render(root: RootPageNode) { + val newRoot = preprocessors.fold(root) { acc, t -> t(acc) } + + locationProvider = + context.single(CoreExtensions.locationProviderFactory).getLocationProvider(newRoot) + + root.children().forEach { renderPackageList(it) } + + renderPages(newRoot) + } +} + +fun ContentPage.platforms() = this.content.platforms.toList() \ No newline at end of file diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt new file mode 100644 index 00000000..c9270681 --- /dev/null +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -0,0 +1,216 @@ +package org.jetbrains.dokka.renderers.html + +import kotlinx.html.* +import kotlinx.html.stream.createHTML +import org.jetbrains.dokka.base.renderers.DefaultRenderer +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.Function +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.renderers.OutputWriter +import java.io.File + +open class HtmlRenderer( + context: DokkaContext +) : DefaultRenderer(context) { + + private val pageList = mutableListOf() + + override val preprocessors = listOf( + RootCreator, + SearchPageInstaller, + ResourceInstaller, + NavigationPageInstaller, + StyleAndScriptsAppender + ) + + override fun FlowContent.buildList(node: ContentList, pageContext: ContentPage) = + if (node.ordered) ol { + buildListItems(node.children, pageContext) + } + else ul { + buildListItems(node.children, pageContext) + } + + open fun OL.buildListItems(items: List, pageContext: ContentPage) { + items.forEach { + if (it is ContentList) + buildList(it, pageContext) + else + li { it.build(this, pageContext) } + } + } + + open fun UL.buildListItems(items: List, pageContext: ContentPage) { + items.forEach { + if (it is ContentList) + buildList(it, pageContext) + else + li { it.build(this, pageContext) } + } + } + + override fun FlowContent.buildResource( + node: ContentEmbeddedResource, + pageContext: ContentPage + ) { // TODO: extension point there + val imageExtensions = setOf("png", "jpg", "jpeg", "gif", "bmp", "tif", "webp", "svg") + return if (File(node.address).extension.toLowerCase() in imageExtensions) { + //TODO: add imgAttrs parsing + val imgAttrs = node.extras.filterIsInstance().joinAttr() + img(src = node.address, alt = node.altText) + } else { + println("Unrecognized resource type: $node") + } + } + + override fun FlowContent.buildTable(node: ContentTable, pageContext: ContentPage) { + table { + thead { + node.header.forEach { + tr { + it.children.forEach { + th { + it.build(this@table, pageContext) + } + } + } + } + } + tbody { + node.children.forEach { + tr { + it.children.forEach { + td { + it.build(this, pageContext) + } + } + } + } + } + } + } + + override fun FlowContent.buildHeader(level: Int, content: FlowContent.() -> Unit) { + when (level) { + 1 -> h1(block = content) + 2 -> h2(block = content) + 3 -> h3(block = content) + 4 -> h4(block = content) + 5 -> h5(block = content) + else -> h6(block = content) + } + } + + override fun FlowContent.buildNavigation(page: PageNode) = + locationProvider.ancestors(page).asReversed().forEach { node -> + text("/") + if (node.isNavigable) buildLink(node, page) + else text(node.name) + } + + private fun FlowContent.buildLink(to: PageNode, from: PageNode) = + buildLink(locationProvider.resolve(to, from)) { + text(to.name) + } + + fun FlowContent.buildLink( + to: DRI, + platforms: List, + from: PageNode? = null, + block: FlowContent.() -> Unit + ) = buildLink(locationProvider.resolve(to, platforms, from), block) + + override fun buildError(node: ContentNode) { + context.logger.error("Unknown ContentNode type: $node") + } + + override fun FlowContent.buildNewLine() { + br() + } + + override fun FlowContent.buildLink(address: String, content: FlowContent.() -> Unit) = + a(href = address, block = content) + + override fun FlowContent.buildCode(code: List, language: String, pageContext: ContentPage) { + buildNewLine() + code.forEach { + +((it as? ContentText)?.text ?: run { context.logger.error("Cannot cast $it as ContentText!"); "" }) + buildNewLine() + } + } + + override fun renderPage(page: PageNode) { + super.renderPage(page) + if (page is ContentPage) { + pageList.add( + """{ "name": "${page.name}", ${if (page is ClasslikePageNode) "\"class\": \"${page.name}\"," else ""} "location": "${locationProvider.resolve( + page + )}" }""" + ) + } + } + + override fun FlowContent.buildText(textNode: ContentText) { + text(textNode.text) + } + + override fun render(root: RootPageNode) { + super.render(root) + outputWriter.write("scripts/pages", "var pages = [\n${pageList.joinToString(",\n")}\n]", ".js") + } + + private fun PageNode.root(path: String) = locationProvider.resolveRoot(this) + path + + override fun buildPage(page: ContentPage, content: (FlowContent, ContentPage) -> Unit): String = + buildHtml(page, page.embeddedResources) { content(this, page) } + + open fun buildHtml(page: PageNode, resources: List, content: FlowContent.() -> Unit) = + createHTML().html { + head { + title(page.name) + with(resources) { + filter { it.substringBefore('?').substringAfterLast('.') == "css" } + .forEach { link(rel = LinkRel.stylesheet, href = page.root(it)) } + filter { it.substringBefore('?').substringAfterLast('.') == "js" } + .forEach { script(type = ScriptType.textJavaScript, src = page.root(it)) { async = true } } + } + script { unsafe { +"""var pathToRoot = "${locationProvider.resolveRoot(page)}";""" } } + } + body { + div { + id = "navigation" + div { + id = "searchBar" + form(action = page.root("-search.html"), method = FormMethod.get) { + id = "searchForm" + input(type = InputType.search, name = "query") + input(type = InputType.submit) { value = "Search" } + } + } + div { + id = "sideMenu" + } + } + div { + id = "content" + content() + } + } + } +} + +fun List.joinAttr() = joinToString(" ") { it.key + "=" + it.value } + +private fun PageNode.pageKind() = when (this) { + is PackagePageNode -> "package" + is ClasslikePageNode -> "class" + is MemberPageNode -> when (this.documentable) { + is Function -> "function" + else -> "other" + } + else -> "other" +} + +private val PageNode.isNavigable: Boolean + get() = this !is RendererSpecificPage || strategy != RenderingStrategy.DoNothing \ No newline at end of file diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt new file mode 100644 index 00000000..4a2fb40d --- /dev/null +++ b/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt @@ -0,0 +1,50 @@ +package org.jetbrains.dokka.renderers.html + +import kotlinx.html.* +import kotlinx.html.stream.createHTML +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.pages.PageNode +import org.jetbrains.dokka.pages.PlatformData +import org.jetbrains.dokka.pages.RendererSpecificPage +import org.jetbrains.dokka.pages.RenderingStrategy + +class NavigationPage(val root: NavigationNode) : RendererSpecificPage { + override val name = "navigation" + + override val children = emptyList() + + override fun modified(name: String, children: List) = this + + override val strategy = RenderingStrategy { + createHTML().visit(root, "nav-submenu", this) + } + + private fun TagConsumer.visit(node: NavigationNode, navId: String, renderer: HtmlRenderer): R = + with(renderer) { + div("sideMenuPart") { + id = navId + div("overview") { + buildLink(node.dri, node.platforms) { +node.name } + if (node.children.isNotEmpty()) { + span("navButton") { + onClick = """document.getElementById("$navId").classList.toggle("hidden");""" + span("navButtonContent") + } + } + } + node.children.withIndex().forEach { (n, p) -> visit(p, "$navId-$n", renderer) } + } + } +} + +class NavigationNode( + val name: String, + val dri: DRI, + val platforms: List, + val children: List +) + +fun NavigationPage.transform(block: (NavigationNode) -> NavigationNode) = NavigationPage(root.transform(block)) + +fun NavigationNode.transform(block: (NavigationNode) -> NavigationNode) = + run(block).let { NavigationNode(it.name, it.dri, it.platforms, it.children.map(block)) } diff --git a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt new file mode 100644 index 00000000..09164d97 --- /dev/null +++ b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt @@ -0,0 +1,68 @@ +package org.jetbrains.dokka.renderers.html + +import kotlinx.html.h1 +import kotlinx.html.id +import kotlinx.html.table +import kotlinx.html.tbody +import org.jetbrains.dokka.base.renderers.platforms +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.transformers.pages.PageNodeTransformer + +object RootCreator : PageNodeTransformer { + override fun invoke(input: RootPageNode) = + RendererSpecificRootPage("", listOf(input), RenderingStrategy.DoNothing) +} + +object SearchPageInstaller : PageNodeTransformer { + override fun invoke(input: RootPageNode) = input.modified(children = input.children + searchPage) + + private val searchPage = RendererSpecificResourcePage( + name = "Search", + children = emptyList(), + strategy = RenderingStrategy { + buildHtml(it, listOf("styles/style.css", "scripts/pages.js")) { + h1 { + id = "searchTitle" + text("Search results for ") + } + table { + tbody { + id = "searchTable" + } + } + } + }) +} + +object NavigationPageInstaller : PageNodeTransformer { + override fun invoke(input: RootPageNode) = input.modified( + children = input.children + NavigationPage( + input.children.filterIsInstance().single().let(::visit) + ) + ) + + private fun visit(page: ContentPage): NavigationNode = NavigationNode( + page.name, + page.dri.first(), + page.platforms(), + page.children.filterIsInstance().map { visit(it) }) +} + +object ResourceInstaller : PageNodeTransformer { + override fun invoke(input: RootPageNode) = input.modified(children = input.children + resourcePages) + + private val resourcePages = listOf("styles", "scripts", "images").map { + RendererSpecificResourcePage(it, emptyList(), RenderingStrategy.Copy("/dokka/$it")) + } +} + +object StyleAndScriptsAppender : PageNodeTransformer { + override fun invoke(input: RootPageNode) = input.transformContentPagesTree { + it.modified( + embeddedResources = it.embeddedResources + listOf( + "styles/style.css", + "scripts/navigationLoader.js" + ) + ) + } +} diff --git a/plugins/base/src/main/kotlin/transformers/psi/DefaultPsiToDocumentationTranslator.kt b/plugins/base/src/main/kotlin/transformers/psi/DefaultPsiToDocumentationTranslator.kt new file mode 100644 index 00000000..b913ae88 --- /dev/null +++ b/plugins/base/src/main/kotlin/transformers/psi/DefaultPsiToDocumentationTranslator.kt @@ -0,0 +1,145 @@ +package org.jetbrains.dokka.base.transformers.psi + +import com.intellij.psi.* +import org.jetbrains.dokka.JavadocParser +import org.jetbrains.dokka.links.Callable +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.Function +import org.jetbrains.dokka.pages.PlatformData +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.transformers.psi.PsiToDocumentationTranslator +import org.jetbrains.dokka.utilities.DokkaLogger +import org.jetbrains.kotlin.descriptors.Visibilities + +object DefaultPsiToDocumentationTranslator : PsiToDocumentationTranslator { + + override fun invoke( + moduleName: String, + psiFiles: List, + platformData: PlatformData, + context: DokkaContext + ): Module { + val docParser = + DokkaPsiParser( + platformData, + context.logger + ) + return Module(moduleName, + psiFiles.map { psiFile -> + val dri = DRI(packageName = psiFile.packageName) + Package( + dri, + emptyList(), + emptyList(), + psiFile.classes.map { docParser.parseClass(it, dri) } + ) + } + ) + } + + class DokkaPsiParser( + private val platformData: PlatformData, + logger: DokkaLogger + ) { + + 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 + ml.any { it.text == PsiKeyword.PROTECTED } -> Visibilities.PROTECTED + else -> Visibilities.PRIVATE + } + } ?: 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 + } + 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 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 { + 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, + JavaTypeWrapper(psiParameter.type), + null, + getComment(psi) + ) + }, + null, + getComment(psi), + visibility = mapOf(platformData to psi.getVisibility()) + ) + } + + private fun parseField(psi: PsiField, parent: DRI): Property { + val dri = parent.copy( + callable = Callable( + psi.name!!, // TODO: Investigate if this is indeed nullable + JavaClassReference(psi.containingClass?.name.orEmpty()), + emptyList() + ) + ) + return Property( + dri, + psi.name!!, // TODO: Investigate if this is indeed nullable + null, + null, + getComment(psi), + accessors = emptyList(), + visibility = mapOf(platformData to psi.getVisibility()) + ) + } + } +} -- cgit From 64b5d214a61dd7964ddad46f3c91475336063d78 Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Tue, 18 Feb 2020 13:20:15 +0100 Subject: Adds bintray publication for dokka base --- .idea/kotlinScripting.xml | 6 ++++++ plugins/base/build.gradle.kts | 9 +++++++++ 2 files changed, 15 insertions(+) create mode 100644 .idea/kotlinScripting.xml (limited to 'plugins/base/build.gradle.kts') diff --git a/.idea/kotlinScripting.xml b/.idea/kotlinScripting.xml new file mode 100644 index 00000000..a6fe551d --- /dev/null +++ b/.idea/kotlinScripting.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/plugins/base/build.gradle.kts b/plugins/base/build.gradle.kts index b42d7e28..fd7ae978 100644 --- a/plugins/base/build.gradle.kts +++ b/plugins/base/build.gradle.kts @@ -1,3 +1,9 @@ +import org.jetbrains.configureBintrayPublication + +plugins { + id("com.jfrog.bintray") +} + publishing { publications { register("basePlugin") { @@ -7,6 +13,9 @@ publishing { } } +configureBintrayPublication("basePlugin") + + dependencies { implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.10") } -- cgit From d41b4c65a0ace7e60f19fc9211947d894a0442f1 Mon Sep 17 00:00:00 2001 From: Błażej Kardyś Date: Wed, 4 Mar 2020 03:30:27 +0100 Subject: Working javadoc parsing --- core/build.gradle.kts | 1 - core/src/main/kotlin/Java/JavadocParser.kt | 369 --------------------- core/src/main/kotlin/links/DRI.kt | 26 +- plugins/base/build.gradle.kts | 4 + plugins/base/src/main/kotlin/DokkaBase.kt | 2 +- .../psi/DefaultPsiToDocumentableTranslator.kt | 319 ------------------ .../psi/DefaultPsiToDocumentableTranslator.kt | 318 ++++++++++++++++++ .../main/kotlin/translators/psi/JavadocParser.kt | 149 +++++++++ 8 files changed, 496 insertions(+), 692 deletions(-) delete mode 100644 core/src/main/kotlin/Java/JavadocParser.kt delete mode 100644 plugins/base/src/main/kotlin/transformers/psi/DefaultPsiToDocumentableTranslator.kt create mode 100644 plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt create mode 100644 plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt (limited to 'plugins/base/build.gradle.kts') diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 365cf7e8..080e1fe6 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -12,7 +12,6 @@ dependencies { api("org.jetbrains.kotlin:kotlin-compiler:$kotlin_version") implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("org.jsoup:jsoup:1.12.1") implementation("com.google.code.gson:gson:2.8.5") testImplementation(project(":testApi")) diff --git a/core/src/main/kotlin/Java/JavadocParser.kt b/core/src/main/kotlin/Java/JavadocParser.kt deleted file mode 100644 index 856cfa04..00000000 --- a/core/src/main/kotlin/Java/JavadocParser.kt +++ /dev/null @@ -1,369 +0,0 @@ -package org.jetbrains.dokka - -import com.intellij.psi.* -import com.intellij.psi.impl.source.tree.JavaDocElementType -import com.intellij.psi.javadoc.* -import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.dokka.model.doc.* -import org.jetbrains.dokka.utilities.DokkaLogger -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 -) : JavaDocumentationParser { - - override fun parseDocumentation(element: PsiNamedElement): DocumentationNode { - val docComment = (element as? PsiDocCommentOwner)?.docComment ?: return DocumentationNode(emptyList()) - val nodes = - convertJavadocElements(docComment.descriptionElements.dropWhile { it.text.trim().isEmpty() }, element) - return DocumentationNode(nodes.map { Description(it) }) - /*val firstParagraphContents = nodes.takeWhile { it !is ContentParagraph } - val firstParagraph = ContentParagraph() - if (firstParagraphContents.isNotEmpty()) { - firstParagraphContents.forEach { firstParagraph.append(it) } - result.add(firstParagraph) - } - - result.appendAll(nodes.drop(firstParagraphContents.size)) - - if (element is PsiMethod) { - val tagsByName = element.searchInheritedTags() - for ((tagName, tags) in tagsByName) { - for ((tag, context) in tags) { - val section = result.addSection(javadocSectionDisplayName(tagName), tag.getSubjectName()) - val signature = signatureProvider.signature(element) - when (tagName) { - "param" -> { - section.appendTypeElement(signature) { - it.details - .find { node -> node.kind == NodeKind.Parameter && node.name == tag.getSubjectName() } - ?.detailOrNull(NodeKind.Type) - } - } - "return" -> { - section.appendTypeElement(signature) { it.detailOrNull(NodeKind.Type) } - } - } - section.appendAll(convertJavadocElements(tag.contentElements(), context)) - } - } - } - - docComment.tags.forEach { tag -> - when (tag.name) { - "see" -> result.convertSeeTag(tag) - "deprecated" -> { - deprecatedContent = Content().apply { - appendAll(convertJavadocElements(tag.contentElements(), element)) - } - } - in tagsToInherit -> {} - else -> { - val subjectName = tag.getSubjectName() - val section = result.addSection(javadocSectionDisplayName(tag.name), subjectName) - - section.appendAll(convertJavadocElements(tag.contentElements(), element)) - } - } - } - return JavadocParseResult(result, deprecatedContent)*/ - } - - private val tagsToInherit = setOf("param", "return", "throws") - - private data class TagWithContext(val tag: PsiDocTag, val context: PsiNamedElement) -/* - private fun PsiMethod.searchInheritedTags(): Map> { - - val output = tagsToInherit.keysToMap { mutableMapOf() } - - fun recursiveSearch(methods: Array) { - for (method in methods) { - recursiveSearch(method.findSuperMethods()) - } - for (method in methods) { - for (tag in method.docComment?.tags.orEmpty()) { - if (tag.name in tagsToInherit) { - output[tag.name]!![tag.getSubjectName()] = TagWithContext(tag, method) - } - } - } - } - - recursiveSearch(arrayOf(this)) - return output.mapValues { it.value.values } - } -*/ - - private fun PsiDocTag.contentElements(): Iterable { - val tagValueElements = children - .dropWhile { it.node?.elementType == JavaDocTokenType.DOC_TAG_NAME } - .dropWhile { it is PsiWhiteSpace } - .filterNot { it.node?.elementType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS } - return if (getSubjectName() != null) tagValueElements.dropWhile { it is PsiDocTagValue } else tagValueElements - } - - private fun convertJavadocElements(elements: Iterable, element: PsiNamedElement): List { - val doc = Jsoup.parse(expandAllForElements(elements, element)) - return doc.body().childNodes().mapNotNull { convertHtmlNode(it) } - } - - private fun expandAllForElements(elements: Iterable, element: PsiNamedElement): String { - val htmlBuilder = StringBuilder() - elements.forEach { - if (it is PsiInlineDocTag) { - htmlBuilder.append(convertInlineDocTag(it, element)) - } else { - htmlBuilder.append(it.text) - } - } - return htmlBuilder.toString().trim() - } - - 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(children) - "b" -> B(children) - "strong" -> Strong(children) - "i" -> I(children) - "em" -> Em(children) -// "s", "del" -> ContentStrikethrough() - "code" -> Code(children) - "pre" -> Pre(children) - "ul" -> Ul(children) - "ol" -> Ol(children) - "li" -> Li(children) -// "a" -> createLink(element) -// "br" -> ContentBlock().apply { hardLineBreak() } - else -> Text(body = element.ownText()) - } - } -/* - - private fun createLink(element: Element): DocTag { - return when { - element.hasAttr("docref") -> { - val docref = element.attr("docref") - ContentNodeLazyLink(docref) { refGraph.lookupOrWarn(docref, logger) } - } - element.hasAttr("href") -> { - val href = element.attr("href") - - val uri = try { - URI(href) - } catch (_: Exception) { - null - } - - if (uri?.isAbsolute == false) { - ContentLocalLink(href) - } else { - ContentExternalLink(href) - } - } - element.hasAttr("name") -> { - ContentBookmark(element.attr("name")) - } - else -> ContentBlock() - } - } - - - private fun convertSeeTag(tag: PsiDocTag) { - val linkElement = tag.linkElement() ?: return - val seeSection = findSectionByTag(ContentTags.SeeAlso) ?: addSection(ContentTags.SeeAlso, null) - - val valueElement = tag.referenceElement() - val externalLink = resolveExternalLink(valueElement) - val text = ContentText(linkElement.text) - - val linkSignature by lazy { resolveInternalLink(valueElement) } - val node = when { - externalLink != null -> { - val linkNode = ContentExternalLink(externalLink) - linkNode.append(text) - linkNode - } - linkSignature != null -> { - val linkNode = - ContentNodeLazyLink( - (tag.valueElement ?: linkElement).text - ) { refGraph.lookupOrWarn(linkSignature!!, logger) } - linkNode.append(text) - linkNode - } - else -> text - } - seeSection.append(node) - } -*/ - private fun convertInlineDocTag(tag: PsiInlineDocTag, element: PsiNamedElement) = when (tag.name) { - "link", "linkplain" -> { - val valueElement = tag.referenceElement() - val externalLink = resolveExternalLink(valueElement) - val linkSignature by lazy { resolveInternalLink(valueElement) } - if (externalLink != null || linkSignature != null) { - val labelText = tag.dataElements.firstOrNull { it is PsiDocToken }?.text ?: valueElement!!.text - val linkTarget = if (externalLink != null) "href=\"$externalLink\"" else "docref=\"$linkSignature\"" - val link = "$labelText" - if (tag.name == "link") "$link" else link - } else if (valueElement != null) { - valueElement.text - } else { - "" - } - } - "code", "literal" -> { - val text = StringBuilder() - tag.dataElements.forEach { text.append(it.text) } - val escaped = text.toString().trimStart() - if (tag.name == "code") "$escaped" else escaped - } - "inheritDoc" -> { - val result = (element as? PsiMethod)?.let { - // @{inheritDoc} is only allowed on functions - val parent = tag.parent - when (parent) { - is PsiDocComment -> element.findSuperDocCommentOrWarn() - is PsiDocTag -> element.findSuperDocTagOrWarn(parent) - else -> null - } - } - result ?: tag.text - } - else -> 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 } - - private fun resolveExternalLink(valueElement: PsiElement?): String? { - /*val target = valueElement?.reference?.resolve() - if (target != null) { - return externalDocumentationLinkResolver.buildExternalDocumentationLink(target) - }*/ - return null - } - - private fun resolveInternalLink(valueElement: PsiElement?): String? { - /*val target = valueElement?.reference?.resolve() - if (target != null) { - return signatureProvider.signature(target) - }*/ - return null - } - - fun PsiDocTag.getSubjectName(): String? { - if (name == "param" || name == "throws" || name == "exception") { - return valueElement?.text - } - return null - } - - private fun PsiMethod.findSuperDocCommentOrWarn(): String { - val method = findFirstSuperMethodWithDocumentation(this) - if (method != null) { - val descriptionElements = method.docComment?.descriptionElements?.dropWhile { - it.text.trim().isEmpty() - } ?: return "" - - return expandAllForElements(descriptionElements, method) - } - logger.warn("No docs found on supertype with {@inheritDoc} method ${this.name} in ${this.containingFile.name}}") - return "" - } - - - private fun PsiMethod.findSuperDocTagOrWarn(elementToExpand: PsiDocTag): String { - val result = findFirstSuperMethodWithDocumentationforTag(elementToExpand, this) - - if (result != null) { - val (method, tag) = result - - val contentElements = tag.contentElements().dropWhile { it.text.trim().isEmpty() } - - val expandedString = expandAllForElements(contentElements, method) - - return expandedString - } - logger.warn("No docs found on supertype for @${elementToExpand.name} ${elementToExpand.getSubjectName()} with {@inheritDoc} method ${this.name} in ${this.containingFile.name}}") - return "" - } - - private fun findFirstSuperMethodWithDocumentation(current: PsiMethod): PsiMethod? { - val superMethods = current.findSuperMethods() - for (method in superMethods) { - val docs = method.docComment?.descriptionElements?.dropWhile { it.text.trim().isEmpty() } - if (!docs.isNullOrEmpty()) { - return method - } - } - for (method in superMethods) { - val result = findFirstSuperMethodWithDocumentation(method) - if (result != null) { - return result - } - } - - return null - } - - private fun findFirstSuperMethodWithDocumentationforTag(elementToExpand: PsiDocTag, current: PsiMethod): Pair? { - val superMethods = current.findSuperMethods() - val mappedFilteredTags = superMethods.map { - it to it.docComment?.tags?.filter { it.name == elementToExpand.name } - } - - for ((method, tags) in mappedFilteredTags) { - tags ?: continue - for (tag in tags) { - val (tagSubject, elementSubject) = when (tag.name) { - "throws" -> { - // match class names only for throws, ignore possibly fully qualified path - // TODO: Always match exactly here - tag.getSubjectName()?.split(".")?.last() to elementToExpand.getSubjectName()?.split(".")?.last() - } - else -> { - tag.getSubjectName() to elementToExpand.getSubjectName() - } - } - - if (tagSubject == elementSubject) { - return method to tag - } - } - } - - for (method in superMethods) { - val result = findFirstSuperMethodWithDocumentationforTag(elementToExpand, method) - if (result != null) { - return result - } - } - return null - } - -} diff --git a/core/src/main/kotlin/links/DRI.kt b/core/src/main/kotlin/links/DRI.kt index aefaf8f6..791d2b5e 100644 --- a/core/src/main/kotlin/links/DRI.kt +++ b/core/src/main/kotlin/links/DRI.kt @@ -1,6 +1,11 @@ package org.jetbrains.dokka.links +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiParameter import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver @@ -36,6 +41,17 @@ data class DRI( ) } + fun from(psi: PsiElement) = psi.parentsWithSelf.run { + val callable = firstIsInstanceOrNull() + val params = (callable?.parameterList?.parameters).orEmpty() + val classes = filterIsInstance().toList() + DRI( + classes.lastOrNull()?.qualifiedName?.substringBeforeLast('.', ""), + classes.toList().takeIf { it.isNotEmpty() }?.asReversed()?.mapNotNull { it.name }?.joinToString("."), + callable?.let { Callable.from(it) }, + firstIsInstanceOrNull()?.let { params.indexOf(it) } + ) + } val topLevel = DRI() } } @@ -67,6 +83,12 @@ data class Callable( valueParameters.mapNotNull { TypeReference.from(it) } ) } + fun from(psi: PsiMethod) = with(psi) { + Callable( + name, + null, + parameterList.parameters.map { param -> JavaClassReference(param.type.canonicalText) }) + } } } @@ -110,7 +132,7 @@ sealed class TypeReference { } } -data class JavaClassReference(val name: String): TypeReference() { +data class JavaClassReference(val name: String) : TypeReference() { override fun toString(): String = name } @@ -132,7 +154,7 @@ data class Nullable(val wrapped: TypeReference) : TypeReference() { override fun toString() = "$wrapped?" } -object StarProjection: TypeReference() { +object StarProjection : TypeReference() { override fun toString() = "*" } diff --git a/plugins/base/build.gradle.kts b/plugins/base/build.gradle.kts index fd7ae978..08f8601e 100644 --- a/plugins/base/build.gradle.kts +++ b/plugins/base/build.gradle.kts @@ -4,6 +4,10 @@ plugins { id("com.jfrog.bintray") } +dependencies { + implementation("org.jsoup:jsoup:1.12.1") +} + publishing { publications { register("basePlugin") { diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt index 489da3ef..03875320 100644 --- a/plugins/base/src/main/kotlin/DokkaBase.kt +++ b/plugins/base/src/main/kotlin/DokkaBase.kt @@ -13,7 +13,7 @@ import org.jetbrains.dokka.base.transformers.pages.merger.FallbackPageMergerStra import org.jetbrains.dokka.base.transformers.pages.merger.PageMerger import org.jetbrains.dokka.base.transformers.pages.merger.PageMergerStrategy import org.jetbrains.dokka.base.transformers.pages.merger.SameMethodNamePageMergerStrategy -import org.jetbrains.dokka.base.transformers.psi.DefaultPsiToDocumentableTranslator +import org.jetbrains.dokka.base.translators.psi.DefaultPsiToDocumentableTranslator import org.jetbrains.dokka.base.translators.descriptors.DefaultDescriptorToDocumentableTranslator import org.jetbrains.dokka.base.translators.documentables.DefaultDocumentableToPageTranslator import org.jetbrains.dokka.plugability.DokkaPlugin diff --git a/plugins/base/src/main/kotlin/transformers/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/transformers/psi/DefaultPsiToDocumentableTranslator.kt deleted file mode 100644 index 4aea45e3..00000000 --- a/plugins/base/src/main/kotlin/transformers/psi/DefaultPsiToDocumentableTranslator.kt +++ /dev/null @@ -1,319 +0,0 @@ -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 -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.properties.PropertyContainer -import org.jetbrains.dokka.pages.PlatformData -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.transformers.psi.PsiToDocumentableTranslator -import org.jetbrains.dokka.utilities.DokkaLogger -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.resolve.DescriptorUtils - -object DefaultPsiToDocumentableTranslator : PsiToDocumentableTranslator { - - override fun invoke( - moduleName: String, - psiFiles: List, - platformData: PlatformData, - context: DokkaContext - ): Module { - val docParser = - DokkaPsiParser( - platformData, - context.logger - ) - return Module( - moduleName, - psiFiles.map { psiFile -> - val dri = DRI(packageName = psiFile.packageName) - Package( - dri, - emptyList(), - emptyList(), - psiFile.classes.map { docParser.parseClasslike(it, dri) }, - emptyList(), - PlatformDependent.empty(), - listOf(platformData) - ) - }, - PlatformDependent.empty(), - listOf(platformData) - ) - } - - class DokkaPsiParser( - private val platformData: PlatformData, - logger: DokkaLogger - ) { - - private val javadocParser: JavadocParser = JavadocParser(logger) - - 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.toPlatformDependant() = - PlatformDependent(mapOf(platformData to this)) - - fun parseClasslike(psi: PsiClass, parent: DRI): Classlike = with(psi) { - val dri = parent.withClass(name.toString()) - 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 (regularFunctions, accessors) = splitFunctionsAndAccessors() - val documentation = javadocParser.parseDocumentation(this).toPlatformDependant() - val allFunctions = regularFunctions.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, accessors[it].orEmpty()) }, - 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, accessors[it].orEmpty()) }, - 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, accessors[it].orEmpty()) }, - 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, accessors[it].orEmpty()) }, - classlikes, - source, - visibility, - null, - mapTypeParameters(dri), - ancestors, - documentation, - getModifier(), - listOf(platformData) - ) - } - } - - 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, - isConstructor, - psi.parameterList.parameters.mapIndexed { index, psiParameter -> - Parameter( - dri.copy(target = index + 1), - psiParameter.name, - javadocParser.parseDocumentation(psiParameter).toPlatformDependant(), - JavaTypeWrapper(psiParameter.type), - listOf(platformData) - ) - }, - javadocParser.parseDocumentation(psi).toPlatformDependant(), - PsiDocumentableSource(psi).toPlatformDependant(), - psi.getVisibility().toPlatformDependant(), - psi.returnType?.let { JavaTypeWrapper(type = it) } ?: JavaTypeWrapper.VOID, - psi.mapTypeParameters(dri), - null, - psi.getModifier(), - listOf(platformData), - PropertyContainer.empty() + InheritedFunction( - isInherited - ) - - ) - } - - private fun PsiModifierListOwner.getModifier() = when { - hasModifier(JvmModifier.ABSTRACT) -> WithAbstraction.Modifier.Abstract - 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 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, MutableMap>> { - val fieldNames = fields.map { it.name to it }.toMap() - val accessors = mutableMapOf>() - val regularMethods = mutableListOf() - 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, parent: DRI, accessors: List): Property { - val dri = parent.copy( - callable = Callable( - psi.name!!, // TODO: Investigate if this is indeed nullable - JavaClassReference(psi.containingClass?.name.orEmpty()), - emptyList() - ) - ) - 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, - accessors.firstOrNull { it.hasParameters() }?.let { parseFunction(it, parent) }, - accessors.firstOrNull { it.returnType == psi.type }?.let { parseFunction(it, parent) }, - psi.getModifier(), - listOf(platformData) - ) - } - } -} 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..3e95865e --- /dev/null +++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt @@ -0,0 +1,318 @@ +package org.jetbrains.dokka.base.translators.psi + +import com.intellij.lang.jvm.JvmModifier +import com.intellij.lang.jvm.types.JvmReferenceType +import com.intellij.psi.* +import org.jetbrains.dokka.links.Callable +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.properties.PropertyContainer +import org.jetbrains.dokka.pages.PlatformData +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.transformers.psi.PsiToDocumentableTranslator +import org.jetbrains.dokka.utilities.DokkaLogger +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.resolve.DescriptorUtils + +object DefaultPsiToDocumentableTranslator : PsiToDocumentableTranslator { + + override fun invoke( + moduleName: String, + psiFiles: List, + platformData: PlatformData, + context: DokkaContext + ): Module { + val docParser = + DokkaPsiParser( + platformData, + context.logger + ) + return Module( + moduleName, + psiFiles.map { psiFile -> + val dri = DRI(packageName = psiFile.packageName) + Package( + dri, + emptyList(), + emptyList(), + psiFile.classes.map { docParser.parseClasslike(it, dri) }, + emptyList(), + PlatformDependent.empty(), + listOf(platformData) + ) + }, + PlatformDependent.empty(), + listOf(platformData) + ) + } + + class DokkaPsiParser( + private val platformData: PlatformData, + logger: DokkaLogger + ) { + + private val javadocParser: JavaDocumentationParser = JavadocParser(logger) + + 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.toPlatformDependant() = + PlatformDependent(mapOf(platformData to this)) + + fun parseClasslike(psi: PsiClass, parent: DRI): Classlike = with(psi) { + val dri = parent.withClass(name.toString()) + 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 (regularFunctions, accessors) = splitFunctionsAndAccessors() + val documentation = javadocParser.parseDocumentation(this).toPlatformDependant() + val allFunctions = regularFunctions.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, accessors[it].orEmpty()) }, + 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, accessors[it].orEmpty()) }, + 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, accessors[it].orEmpty()) }, + 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, accessors[it].orEmpty()) }, + classlikes, + source, + visibility, + null, + mapTypeParameters(dri), + ancestors, + documentation, + getModifier(), + listOf(platformData) + ) + } + } + + 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, + isConstructor, + psi.parameterList.parameters.mapIndexed { index, psiParameter -> + Parameter( + dri.copy(target = index + 1), + psiParameter.name, + javadocParser.parseDocumentation(psiParameter).toPlatformDependant(), + JavaTypeWrapper(psiParameter.type), + listOf(platformData) + ) + }, + javadocParser.parseDocumentation(psi).toPlatformDependant(), + PsiDocumentableSource(psi).toPlatformDependant(), + psi.getVisibility().toPlatformDependant(), + psi.returnType?.let { JavaTypeWrapper(type = it) } ?: JavaTypeWrapper.VOID, + psi.mapTypeParameters(dri), + null, + psi.getModifier(), + listOf(platformData), + PropertyContainer.empty() + InheritedFunction( + isInherited + ) + + ) + } + + private fun PsiModifierListOwner.getModifier() = when { + hasModifier(JvmModifier.ABSTRACT) -> WithAbstraction.Modifier.Abstract + 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 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, MutableMap>> { + val fieldNames = fields.map { it.name to it }.toMap() + val accessors = mutableMapOf>() + val regularMethods = mutableListOf() + 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, parent: DRI, accessors: List): Property { + val dri = parent.copy( + callable = Callable( + psi.name!!, // TODO: Investigate if this is indeed nullable + JavaClassReference(psi.containingClass?.name.orEmpty()), + emptyList() + ) + ) + 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, + accessors.firstOrNull { it.hasParameters() }?.let { parseFunction(it, parent) }, + accessors.firstOrNull { it.returnType == psi.type }?.let { parseFunction(it, parent) }, + psi.getModifier(), + listOf(platformData) + ) + } + } +} 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..5b9af028 --- /dev/null +++ b/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt @@ -0,0 +1,149 @@ +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.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.utils.addToStdlib.firstIsInstanceOrNull +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 = (element as? PsiDocCommentOwner)?.docComment ?: return DocumentationNode(emptyList()) + val nodes = mutableListOf() + 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()) + "deprecated" -> Deprecated(P(convertJavadocElements(tag.dataElements.toList()))) + else -> null + } + }) + return DocumentationNode(nodes) + } + + private fun getSeeTagElementContent(tag: PsiDocTag): List = + listOfNotNull(tag.referenceElement()?.toDocumentationLink()) + + private fun PsiDocComment.getDescription(): Description? = + convertJavadocElements(descriptionElements.dropWhile { + it.text.trim().isEmpty() + }).takeIf { it.isNotEmpty() }?.let { list -> + Description(P(list)) + } + + private fun convertJavadocElements(elements: Iterable): List = + elements.mapNotNull { + when (it) { + is PsiReference -> convertJavadocElements(it.children.toList()) + is PsiInlineDocTag -> listOfNotNull(convertInlineDocTag(it)) + is PsiDocParamRef -> listOfNotNull(it.toDocumentationLink()) + is PsiDocTagValue, + is PsiWhiteSpace -> listOfNotNull(Text(it.text)) + is LeafPsiElement -> Jsoup.parse(it.text).body().childNodes().mapNotNull { convertHtmlNode(it) } + 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(children) + "b" -> B(children) + "strong" -> Strong(children) + "i" -> I(children) + "em" -> Em(children) + "code" -> Code(children) + "pre" -> Pre(children) + "ul" -> Ul(children) + "ol" -> Ol(children) + "li" -> Li(children) + //"a" -> createLink(element, children) // TODO: add proper inline link handling + "br" -> Br + else -> Text(body = element.ownText()) + } + } +/* + + private fun createLink(element: Element, children: List): DocTag { + return when { + element.hasAttr("docref") -> { + A(children, params = mapOf("docref" to element.attr("docref"))) + } + element.hasAttr("href") -> { + val href = element.attr("href") + + val uri = try { + A(children, params = mapOf("href" to href)) + } catch (_: Exception) { + null + } + + if (uri?.isAbsolute == false) { + ContentLocalLink(href) + } else { + ContentExternalLink(href) + } + } + element.hasAttr("name") -> { + ContentBookmark(element.attr("name")) + } + else -> Text() + } + }*/ + + 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() } ?: this + DocumentationLink(dri, convertJavadocElements(listOfNotNull(label))) + } + + private fun convertInlineDocTag(tag: PsiInlineDocTag) = when (tag.name) { + "link", "linkplain" -> { + tag.referenceElement()?.toDocumentationLink(tag.dataElements.firstIsInstanceOrNull()) + } + "code", "literal" -> { + Code(listOf(Text(tag.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 } +} -- cgit From 810f3c922fb4f11dc3fbdddee82d919189ed8526 Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Thu, 26 Mar 2020 13:17:38 +0100 Subject: Adds simple tests for parameter rendering --- plugins/base/build.gradle.kts | 1 + .../kotlin/content/params/ContentForParamsTest.kt | 407 +++++++++++++++++++++ plugins/base/src/test/kotlin/utils/contentUtils.kt | 56 +++ test-tools/build.gradle.kts | 1 + .../kotlin/matchers/content/ContentMatchersDsl.kt | 41 ++- .../kotlin/matchers/content/contentMatchers.kt | 110 ++++-- 6 files changed, 584 insertions(+), 32 deletions(-) create mode 100644 plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt create mode 100644 plugins/base/src/test/kotlin/utils/contentUtils.kt (limited to 'plugins/base/build.gradle.kts') diff --git a/plugins/base/build.gradle.kts b/plugins/base/build.gradle.kts index 08f8601e..1a3d6c1d 100644 --- a/plugins/base/build.gradle.kts +++ b/plugins/base/build.gradle.kts @@ -6,6 +6,7 @@ plugins { dependencies { implementation("org.jsoup:jsoup:1.12.1") + testImplementation(project(":test-tools")) } publishing { diff --git a/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt b/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt new file mode 100644 index 00000000..9f8ebb00 --- /dev/null +++ b/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt @@ -0,0 +1,407 @@ +package content.params + +import matchers.content.* +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Test +import utils.pWrapped +import utils.signature +import utils.signatureWithReceiver + +class ContentForParamsTest : AbstractCoreTest() { + private val testConfiguration = dokkaConfiguration { + passes { + pass { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + targets = listOf("jvm") + } + } + } + + @Test + fun `undocumented function`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + header(1) { +"function" } + signature("function", null, "abc" to "String") + } + } + } + } + + @Test + fun `undocumented parameter`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + header(1) { +"function" } + signature("function", null, "abc" to "String") + header(3) { +"Description" } + platformHinted { + pWrapped("comment to function") + } + } + } + } + } + + @Test + fun `undocumented parameter and other tags without function comment`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @author Kordyjan + | * @since 0.11 + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + header(1) { +"function" } + signature("function", null, "abc" to "String") + header(3) { +"Description" } + platformHinted { + header(4) { +"Author" } + +"Kordyjan" + header(4) { +"Since" } + +"0.11" + } + } + } + } + } + + @Test + fun `undocumented parameter and other tags`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | * @author Kordyjan + | * @since 0.11 + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + header(1) { +"function" } + signature("function", null, "abc" to "String") + header(3) { +"Description" } + platformHinted { + pWrapped("comment to function") + header(4) { +"Author" } + +"Kordyjan" + header(4) { +"Since" } + +"0.11" + } + } + } + } + } + + @Test + fun `single parameter`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | * @param abc comment to param + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + header(1) { +"function" } + signature("function", null, "abc" to "String") + header(3) { +"Description" } + platformHinted { + pWrapped("comment to function") + header(4) { +"Parameters" } + table { + group { + +"abc" + +"comment to param" + } + } + } + } + } + } + } + + @Test + fun `multiple parameters`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | * @param first comment to first param + | * @param second comment to second param + | * @param[third] comment to third param + | */ + |fun function(first: String, second: Int, third: Double) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + header(1) { +"function" } + signature("function", null, "first" to "String", "second" to "Int", "third" to "Double") + header(3) { +"Description" } + platformHinted { + pWrapped("comment to function") + header(4) { +"Parameters" } + table { + group { + +"first" + +"comment to first param" + } + group { + +"second" + +"comment to second param" + } + group { + +"third" + +"comment to third param" + } + } + } + } + } + } + } + + @Test + fun `multiple parameters without function description`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @param first comment to first param + | * @param second comment to second param + | * @param[third] comment to third param + | */ + |fun function(first: String, second: Int, third: Double) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + header(1) { +"function" } + signature("function", null, "first" to "String", "second" to "Int", "third" to "Double") + header(3) { +"Description" } + platformHinted { + header(4) { +"Parameters" } + table { + group { + +"first" + +"comment to first param" + } + group { + +"second" + +"comment to second param" + } + group { + +"third" + +"comment to third param" + } + } + } + } + } + } + } + + @Test + fun `function with receiver`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | * @param abc comment to param + | * @receiver comment to receiver + | */ + |fun String.function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + header(1) { +"function" } + signatureWithReceiver("String", "function", null, "abc" to "String") + header(3) { +"Description" } + platformHinted { + pWrapped("comment to function") + header(4) { +"Parameters" } + table { + group { + +"" + +"comment to receiver" + } + group { + +"abc" + +"comment to param" + } + } + } + } + } + } + } + + @Test + fun `missing parameter documentation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | * @param first comment to first param + | * @param[third] comment to third param + | */ + |fun function(first: String, second: Int, third: Double) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + header(1) { +"function" } + signature("function", null, "first" to "String", "second" to "Int", "third" to "Double") + header(3) { +"Description" } + platformHinted { + pWrapped("comment to function") + header(4) { +"Parameters" } + table { + group { + +"first" + +"comment to first param" + } + group { + +"third" + +"comment to third param" + } + } + } + } + } + } + } + + @Test + fun `parameters mixed with other tags`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | * @param first comment to first param + | * @author Kordyjan + | * @param second comment to second param + | * @since 0.11 + | * @param[third] comment to third param + | */ + |fun function(first: String, second: Int, third: Double) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + header(1) { +"function" } + signature("function", null, "first" to "String", "second" to "Int", "third" to "Double") + header(3) { +"Description" } + platformHinted { + pWrapped("comment to function") + header(4) { +"Parameters" } + table { + group { + +"first" + +"comment to first param" + } + group { + +"second" + +"comment to second param" + } + group { + +"third" + +"comment to third param" + } + } + header(4) { +"Author" } + +"Kordyjan" + header(4) { +"Since" } + +"0.11" + } + } + } + } + } +} \ No newline at end of file diff --git a/plugins/base/src/test/kotlin/utils/contentUtils.kt b/plugins/base/src/test/kotlin/utils/contentUtils.kt new file mode 100644 index 00000000..4bb36553 --- /dev/null +++ b/plugins/base/src/test/kotlin/utils/contentUtils.kt @@ -0,0 +1,56 @@ +package utils + +import matchers.content.* + +//TODO: Try to unify those functions after update to 1.4 +fun ContentMatcherBuilder<*>.signature( + name: String, + returnType: String? = null, + vararg params: Pair +) = + platformHinted { + group { // TODO: remove it when double wrapping for signatures will be resolved + +"final fun" + link { +name } + +"(" + params.forEachIndexed { id, (n, t) -> + +"$n:" + group { link { +t } } + if (id != params.lastIndex) + +", " + } + +")" + returnType?.let { +": $it" } + } + } + +fun ContentMatcherBuilder<*>.signatureWithReceiver( + receiver: String, + name: String, + returnType: String? = null, + vararg params: Pair +) = + platformHinted { + group { // TODO: remove it when double wrapping for signatures will be resolved + +"final fun" + group { + link { +receiver } + } + +"." + link { +name } + +"(" + params.forEach { (n, t) -> + +"$n:" + group { link { +t } } + } + +")" + returnType?.let { +": $it" } + } + } + + +fun ContentMatcherBuilder<*>.pWrapped(text: String) = + group {// TODO: remove it when double wrapping for descriptions will be resolved + group { +text } + br() + } \ No newline at end of file diff --git a/test-tools/build.gradle.kts b/test-tools/build.gradle.kts index 4f6d2500..7fd8a879 100644 --- a/test-tools/build.gradle.kts +++ b/test-tools/build.gradle.kts @@ -1,4 +1,5 @@ dependencies { implementation(project(":testApi")) implementation(kotlin("stdlib-jdk8")) + implementation("com.willowtreeapps.assertk:assertk-jvm:0.22") } \ No newline at end of file diff --git a/test-tools/src/main/kotlin/matchers/content/ContentMatchersDsl.kt b/test-tools/src/main/kotlin/matchers/content/ContentMatchersDsl.kt index ae3ecab7..41d73378 100644 --- a/test-tools/src/main/kotlin/matchers/content/ContentMatchersDsl.kt +++ b/test-tools/src/main/kotlin/matchers/content/ContentMatchersDsl.kt @@ -1,14 +1,20 @@ package matchers.content -import org.jetbrains.dokka.pages.ContentComposite -import org.jetbrains.dokka.pages.ContentGroup -import org.jetbrains.dokka.pages.ContentNode +import assertk.assertThat +import assertk.assertions.contains +import assertk.assertions.isEqualTo +import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.test.tools.matchers.content.* import kotlin.reflect.KClass // entry point: fun ContentNode.assertNode(block: ContentMatcherBuilder.() -> Unit) { - ContentMatcherBuilder(ContentComposite::class).apply(block).build().tryMatch(this) + val matcher = ContentMatcherBuilder(ContentComposite::class).apply(block).build() + try { + matcher.tryMatch(this) + } catch (e: MatcherError) { + throw AssertionError(e.message + "\n" + matcher.toDebugString(e.anchor, e.anchorAfter)) + } } @@ -40,7 +46,7 @@ inline fun ContentMatcherBuilder<*>.composite( children += ContentMatcherBuilder(S::class).apply(block).build() } -inline fun ContentMatcherBuilder<*>.node(noinline assertions: S.() -> Unit) { +inline fun ContentMatcherBuilder<*>.node(noinline assertions: S.() -> Unit = {}) { children += NodeMatcher(S::class, assertions) } @@ -50,9 +56,28 @@ fun ContentMatcherBuilder<*>.skipAllNotMatching() { // Convenience functions: -fun ContentMatcherBuilder<*>.group(block: ContentMatcherBuilder.() -> Unit) { - children += ContentMatcherBuilder(ContentGroup::class).apply(block).build() -} +fun ContentMatcherBuilder<*>.group(block: ContentMatcherBuilder.() -> Unit) = composite(block) + +fun ContentMatcherBuilder<*>.header(expectedLevel: Int? = null, block: ContentMatcherBuilder.() -> Unit) = + composite { + block() + check { if (expectedLevel != null) assertThat(this::level).isEqualTo(expectedLevel) } + } + +fun ContentMatcherBuilder<*>.p(block: ContentMatcherBuilder.() -> Unit) = + composite { + block() + check { assertThat(this::style).contains(TextStyle.Paragraph) } + } + +fun ContentMatcherBuilder<*>.link(block: ContentMatcherBuilder.() -> Unit) = composite(block) + +fun ContentMatcherBuilder<*>.table(block: ContentMatcherBuilder.() -> Unit) = composite(block) + +fun ContentMatcherBuilder<*>.platformHinted(block: ContentMatcherBuilder.() -> Unit) = + composite { group(block) } + +fun ContentMatcherBuilder<*>.br() = node() fun ContentMatcherBuilder<*>.somewhere(block: ContentMatcherBuilder<*>.() -> Unit) { skipAllNotMatching() diff --git a/test-tools/src/main/kotlin/matchers/content/contentMatchers.kt b/test-tools/src/main/kotlin/matchers/content/contentMatchers.kt index c4400646..2284c88d 100644 --- a/test-tools/src/main/kotlin/matchers/content/contentMatchers.kt +++ b/test-tools/src/main/kotlin/matchers/content/contentMatchers.kt @@ -5,17 +5,24 @@ import org.jetbrains.dokka.pages.ContentNode import org.jetbrains.dokka.pages.ContentText import kotlin.reflect.KClass import kotlin.reflect.full.cast +import kotlin.reflect.full.safeCast sealed class MatcherElement class TextMatcher(val text: String) : MatcherElement() -open class NodeMatcher( +open class NodeMatcher( val kclass: KClass, val assertions: T.() -> Unit = {} -): MatcherElement() { +) : MatcherElement() { open fun tryMatch(node: ContentNode) { - assertions(kclass.cast(node)) + kclass.safeCast(node)?.apply { + try { + assertions() + } catch (e: AssertionError) { + throw MatcherError(e.message.orEmpty(), this@NodeMatcher, cause = e) + } + } ?: throw MatcherError("Expected ${kclass.simpleName} but got: $node", this) } } @@ -24,7 +31,7 @@ class CompositeMatcher( private val children: List, assertions: T.() -> Unit = {} ) : NodeMatcher(kclass, assertions) { - private val normalizedChildren: List by lazy { + internal val normalizedChildren: List by lazy { children.fold(listOf()) { acc, e -> when { acc.lastOrNull() is Anything && e is Anything -> acc @@ -37,7 +44,9 @@ class CompositeMatcher( override fun tryMatch(node: ContentNode) { super.tryMatch(node) - kclass.cast(node).children.fold(normalizedChildren.pop()) { acc, n -> acc.next(n) }.finish() + kclass.cast(node).children.asSequence() + .filter { it !is ContentText || it.text.isNotBlank() } + .fold(FurtherSiblings(normalizedChildren, this).pop()) { acc, n -> acc.next(n) }.finish() } } @@ -48,22 +57,27 @@ private sealed class MatchWalkerState { abstract fun finish() } -private class TextMatcherState(val text: String, val rest: List) : MatchWalkerState() { +private class TextMatcherState( + val text: String, + val rest: FurtherSiblings, + val anchor: TextMatcher +) : MatchWalkerState() { override fun next(node: ContentNode): MatchWalkerState { - node as ContentText + node as? ContentText ?: throw MatcherError("Expected text: \"$text\" but got $node", anchor) + val trimmed = node.text.trim() return when { - text == node.text -> rest.pop() - text.startsWith(node.text) -> TextMatcherState(text.removePrefix(node.text), rest) - else -> throw AssertionError("Expected text: \"$text\", but found: \"${node.text}\"") + text == trimmed -> rest.pop() + text.startsWith(trimmed) -> TextMatcherState(text.removePrefix(node.text).trim(), rest, anchor) + else -> throw MatcherError("Expected text: \"$text\", but got: \"${node.text}\"", anchor) } } - override fun finish() = throw AssertionError("\"$text\" was not found" + rest.messageEnd) + override fun finish() = throw MatcherError("\"$text\" was not found" + rest.messageEnd, anchor) } -private object EmptyMatcherState : MatchWalkerState() { +private class EmptyMatcherState(val parent: CompositeMatcher<*>) : MatchWalkerState() { override fun next(node: ContentNode): MatchWalkerState { - throw AssertionError("Unexpected $node") + throw MatcherError("Unexpected $node", parent, anchorAfter = true) } override fun finish() = Unit @@ -71,14 +85,15 @@ private object EmptyMatcherState : MatchWalkerState() { private class NodeMatcherState( val matcher: NodeMatcher<*>, - val rest: List + val rest: FurtherSiblings ) : MatchWalkerState() { override fun next(node: ContentNode): MatchWalkerState { matcher.tryMatch(node) return rest.pop() } - override fun finish() = throw AssertionError("Composite of type ${matcher.kclass} was not found" + rest.messageEnd) + override fun finish() = + throw MatcherError("Content of type ${matcher.kclass} was not found" + rest.messageEnd, matcher) } private class SkippingMatcherState( @@ -89,17 +104,64 @@ private class SkippingMatcherState( override fun finish() = innerState.finish() } -private fun List.pop(): MatchWalkerState = when (val head = firstOrNull()) { - is TextMatcher -> TextMatcherState(head.text, drop(1)) - is NodeMatcher<*> -> NodeMatcherState(head, drop(1)) - is Anything -> SkippingMatcherState(drop(1).pop()) - null -> EmptyMatcherState +private class FurtherSiblings(val list: List, val parent: CompositeMatcher<*>) { + fun pop(): MatchWalkerState = when (val head = list.firstOrNull()) { + is TextMatcher -> TextMatcherState(head.text.trim(), drop(), head) + is NodeMatcher<*> -> NodeMatcherState(head, drop()) + is Anything -> SkippingMatcherState(drop().pop()) + null -> EmptyMatcherState(parent) + } + + fun drop() = FurtherSiblings(list.drop(1), parent) + + val messageEnd: String + get() = list.filter { it !is Anything } + .count().takeIf { it > 0 } + ?.let { " and $it further matchers were not satisfied" } ?: "" +} + + + +internal fun MatcherElement.toDebugString(anchor: MatcherElement?, anchorAfter: Boolean): String { + fun Appendable.append(element: MatcherElement, ownPrefix: String, childPrefix: String) { + if (anchor != null) { + if (element != anchor || anchorAfter) append(" ".repeat(4)) + else append("--> ") + } + + append(ownPrefix) + when (element) { + is Anything -> append("skipAllNotMatching\n") + is TextMatcher -> append("\"${element.text}\"\n") + is CompositeMatcher<*> -> { + append("${element.kclass.simpleName.toString()}\n") + if (element.normalizedChildren.isNotEmpty()) { + val newOwnPrefix = childPrefix + '\u251c' + '\u2500' + ' ' + val lastOwnPrefix = childPrefix + '\u2514' + '\u2500' + ' ' + val newChildPrefix = childPrefix + '\u2502' + ' ' + ' ' + val lastChildPrefix = childPrefix + ' ' + ' ' + ' ' + element.normalizedChildren.forEachIndexed { n, e -> + if (n != element.normalizedChildren.lastIndex) append(e, newOwnPrefix, newChildPrefix) + else append(e, lastOwnPrefix, lastChildPrefix) + } + } + if (element == anchor && anchorAfter) { + append("--> $childPrefix\n") + } + } + is NodeMatcher<*> -> append("${element.kclass.simpleName}\n") + } + } + + return buildString { append(this@toDebugString, "", "") } } -private val List.messageEnd: String - get() = filter { it !is Anything } - .count().takeIf { it > 0 } - ?.let { " and $it further matchers were not satisfied" } ?: "" +data class MatcherError( + override val message: String, + val anchor: MatcherElement, + val anchorAfter: Boolean = false, + override val cause: Throwable? = null +) : AssertionError(message, cause) // Creating this whole mechanism was most scala-like experience I had since I stopped using scala. // I don't know how I should feel about it. \ No newline at end of file -- cgit From 940988c55f4bd0af58c26762b021409191a5e4d3 Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Tue, 31 Mar 2020 15:58:44 +0200 Subject: Merge redundant dependencies block in build definition --- plugins/base/build.gradle.kts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'plugins/base/build.gradle.kts') diff --git a/plugins/base/build.gradle.kts b/plugins/base/build.gradle.kts index 1a3d6c1d..322560c6 100644 --- a/plugins/base/build.gradle.kts +++ b/plugins/base/build.gradle.kts @@ -6,6 +6,7 @@ plugins { dependencies { implementation("org.jsoup:jsoup:1.12.1") + implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.10") testImplementation(project(":test-tools")) } @@ -19,9 +20,3 @@ publishing { } configureBintrayPublication("basePlugin") - - -dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.10") -} - -- cgit From 25d826bb75a78eb674a63aed19f55e92d7ff8bca Mon Sep 17 00:00:00 2001 From: Filip Zybała Date: Tue, 12 May 2020 14:58:05 +0200 Subject: Migrated resources to base-plugin --- core/build.gradle.kts | 11 - core/search-component/.gitignore | 3 - core/search-component/build.gradle.kts | 18 - core/search-component/package.json | 80 --- core/search-component/postcss.config.js | 17 - .../src/main/js/search/app-root.js | 47 -- core/search-component/src/main/js/search/app.css | 21 - core/search-component/src/main/js/search/app.js | 41 -- core/search-component/src/main/js/search/search.js | 52 -- core/search-component/stylelint.config.js | 4 - core/search-component/webpack.config.js | 62 -- .../src/main/resources/dokka/format/gfm.properties | 2 - .../resources/dokka/format/html-as-java.properties | 2 - .../main/resources/dokka/format/html.properties | 2 - .../dokka/format/java-layout-html.properties | 2 - .../main/resources/dokka/format/jekyll.properties | 2 - .../dokka/format/kotlin-website-html.properties | 2 - .../resources/dokka/format/markdown.properties | 2 - .../src/main/resources/dokka/images/arrow_down.svg | 3 - core/src/main/resources/dokka/images/docs_logo.svg | 7 - core/src/main/resources/dokka/images/logo-icon.svg | 3 - core/src/main/resources/dokka/images/logo-text.svg | 6 - .../inbound-link-resolver/dokka-default.properties | 2 - .../java-layout-html.properties | 2 - .../dokka/inbound-link-resolver/javadoc.properties | 2 - .../resources/dokka/scripts/navigationLoader.js | 43 -- .../dokka/scripts/platformContentHandler.js | 52 -- core/src/main/resources/dokka/scripts/scripts.js | 11 - core/src/main/resources/dokka/scripts/search.js | 7 - .../main/resources/dokka/styles/jetbrains-mono.css | 13 - core/src/main/resources/dokka/styles/style.css | 735 --------------------- plugins/base/build.gradle.kts | 11 + plugins/base/search-component/.gitignore | 3 + plugins/base/search-component/build.gradle.kts | 18 + plugins/base/search-component/package.json | 80 +++ plugins/base/search-component/postcss.config.js | 17 + .../src/main/js/search/app-root.js | 47 ++ .../search-component/src/main/js/search/app.css | 21 + .../search-component/src/main/js/search/app.js | 41 ++ .../search-component/src/main/js/search/search.js | 52 ++ plugins/base/search-component/stylelint.config.js | 4 + plugins/base/search-component/webpack.config.js | 62 ++ .../base/src/main/kotlin/renderers/FileWriter.kt | 2 +- .../src/main/resources/dokka/format/gfm.properties | 2 + .../resources/dokka/format/html-as-java.properties | 2 + .../main/resources/dokka/format/html.properties | 2 + .../dokka/format/java-layout-html.properties | 2 + .../main/resources/dokka/format/jekyll.properties | 2 + .../dokka/format/kotlin-website-html.properties | 2 + .../resources/dokka/format/markdown.properties | 2 + .../src/main/resources/dokka/images/arrow_down.svg | 3 + .../src/main/resources/dokka/images/docs_logo.svg | 7 + .../src/main/resources/dokka/images/logo-icon.svg | 3 + .../src/main/resources/dokka/images/logo-text.svg | 6 + .../inbound-link-resolver/dokka-default.properties | 2 + .../java-layout-html.properties | 2 + .../dokka/inbound-link-resolver/javadoc.properties | 2 + .../resources/dokka/scripts/navigationLoader.js | 43 ++ .../dokka/scripts/platformContentHandler.js | 52 ++ .../src/main/resources/dokka/scripts/scripts.js | 11 + .../src/main/resources/dokka/scripts/search.js | 7 + .../main/resources/dokka/styles/jetbrains-mono.css | 13 + .../base/src/main/resources/dokka/styles/style.css | 735 +++++++++++++++++++++ settings.gradle.kts | 2 + 64 files changed, 1259 insertions(+), 1257 deletions(-) delete mode 100644 core/search-component/.gitignore delete mode 100644 core/search-component/build.gradle.kts delete mode 100644 core/search-component/package.json delete mode 100644 core/search-component/postcss.config.js delete mode 100644 core/search-component/src/main/js/search/app-root.js delete mode 100644 core/search-component/src/main/js/search/app.css delete mode 100644 core/search-component/src/main/js/search/app.js delete mode 100644 core/search-component/src/main/js/search/search.js delete mode 100644 core/search-component/stylelint.config.js delete mode 100644 core/search-component/webpack.config.js delete mode 100644 core/src/main/resources/dokka/format/gfm.properties delete mode 100644 core/src/main/resources/dokka/format/html-as-java.properties delete mode 100644 core/src/main/resources/dokka/format/html.properties delete mode 100644 core/src/main/resources/dokka/format/java-layout-html.properties delete mode 100644 core/src/main/resources/dokka/format/jekyll.properties delete mode 100644 core/src/main/resources/dokka/format/kotlin-website-html.properties delete mode 100644 core/src/main/resources/dokka/format/markdown.properties delete mode 100755 core/src/main/resources/dokka/images/arrow_down.svg delete mode 100644 core/src/main/resources/dokka/images/docs_logo.svg delete mode 100755 core/src/main/resources/dokka/images/logo-icon.svg delete mode 100755 core/src/main/resources/dokka/images/logo-text.svg delete mode 100644 core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties delete mode 100644 core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties delete mode 100644 core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties delete mode 100644 core/src/main/resources/dokka/scripts/navigationLoader.js delete mode 100644 core/src/main/resources/dokka/scripts/platformContentHandler.js delete mode 100644 core/src/main/resources/dokka/scripts/scripts.js delete mode 100644 core/src/main/resources/dokka/scripts/search.js delete mode 100644 core/src/main/resources/dokka/styles/jetbrains-mono.css delete mode 100644 core/src/main/resources/dokka/styles/style.css create mode 100644 plugins/base/search-component/.gitignore create mode 100644 plugins/base/search-component/build.gradle.kts create mode 100644 plugins/base/search-component/package.json create mode 100644 plugins/base/search-component/postcss.config.js create mode 100644 plugins/base/search-component/src/main/js/search/app-root.js create mode 100644 plugins/base/search-component/src/main/js/search/app.css create mode 100644 plugins/base/search-component/src/main/js/search/app.js create mode 100644 plugins/base/search-component/src/main/js/search/search.js create mode 100644 plugins/base/search-component/stylelint.config.js create mode 100644 plugins/base/search-component/webpack.config.js create mode 100644 plugins/base/src/main/resources/dokka/format/gfm.properties create mode 100644 plugins/base/src/main/resources/dokka/format/html-as-java.properties create mode 100644 plugins/base/src/main/resources/dokka/format/html.properties create mode 100644 plugins/base/src/main/resources/dokka/format/java-layout-html.properties create mode 100644 plugins/base/src/main/resources/dokka/format/jekyll.properties create mode 100644 plugins/base/src/main/resources/dokka/format/kotlin-website-html.properties create mode 100644 plugins/base/src/main/resources/dokka/format/markdown.properties create mode 100755 plugins/base/src/main/resources/dokka/images/arrow_down.svg create mode 100644 plugins/base/src/main/resources/dokka/images/docs_logo.svg create mode 100755 plugins/base/src/main/resources/dokka/images/logo-icon.svg create mode 100755 plugins/base/src/main/resources/dokka/images/logo-text.svg create mode 100644 plugins/base/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties create mode 100644 plugins/base/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties create mode 100644 plugins/base/src/main/resources/dokka/inbound-link-resolver/javadoc.properties create mode 100644 plugins/base/src/main/resources/dokka/scripts/navigationLoader.js create mode 100644 plugins/base/src/main/resources/dokka/scripts/platformContentHandler.js create mode 100644 plugins/base/src/main/resources/dokka/scripts/scripts.js create mode 100644 plugins/base/src/main/resources/dokka/scripts/search.js create mode 100644 plugins/base/src/main/resources/dokka/styles/jetbrains-mono.css create mode 100644 plugins/base/src/main/resources/dokka/styles/style.css (limited to 'plugins/base/build.gradle.kts') diff --git a/core/build.gradle.kts b/core/build.gradle.kts index bcc74480..14718cfe 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -24,17 +24,6 @@ val sourceJar by tasks.registering(Jar::class) { from(sourceSets["main"].allSource) } -task("copy_search", Copy::class) { - from(File(project(":core:search-component").projectDir, "dist/")) - destinationDir = File(sourceSets.main.get().resources.sourceDirectories.singleFile, "dokka/scripts") -}.dependsOn(":core:search-component:generateSearchFiles") - -tasks { - processResources { - dependsOn("copy_search") - } -} - publishing { publications { register("dokkaCore") { diff --git a/core/search-component/.gitignore b/core/search-component/.gitignore deleted file mode 100644 index 9220c11f..00000000 --- a/core/search-component/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/dist/ -/node_modules/ -/package-lock.json diff --git a/core/search-component/build.gradle.kts b/core/search-component/build.gradle.kts deleted file mode 100644 index ce747c5e..00000000 --- a/core/search-component/build.gradle.kts +++ /dev/null @@ -1,18 +0,0 @@ -plugins { - id("com.moowork.node") version "1.3.1" -} - -task("generateSearchFiles") { - dependsOn("npm_install", "npm_run_build") -} - -tasks { - "npm_run_build" { - inputs.dir("$projectDir/src/main/js/search/") - inputs.files("$projectDir/package.json", "$projectDir/webpack.config.js") - outputs.dir("$projectDir/dist/") - } - clean { - delete = setOf("$projectDir/node_modules", "$projectDir/dist/", "$projectDir/package-lock.json") - } -} \ No newline at end of file diff --git a/core/search-component/package.json b/core/search-component/package.json deleted file mode 100644 index fa997c44..00000000 --- a/core/search-component/package.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "name": "search", - "version": "1.0.0", - "private": true, - "config": { - "components": "./src/main/js/search", - "dist": "./dist" - }, - "scripts": { - "create-component": "yo @jetbrains/ring-ui:react --path $npm_package_config_components", - "build": "webpack --mode=production --devtool sourcemap", - "test": "karma start", - "lint": "eslint . && npm run stylelint", - "stylelint": "stylelint --ignore-path .gitignore **/*.css", - "ci-test": "npm run lint && xvfb-maybe karma start --teamcity", - "start": "webpack-dev-server -d --history-api-fallback --inline --hot --colors --port 9010" - }, - "babel": { - "presets": [ - [ - "@jetbrains/jetbrains", - { - "useBuiltIns": "usage" - } - ] - ] - }, - "dependencies": { - "@babel/core": "^7.8.3", - "@jetbrains/babel-preset-jetbrains": "^2.1.4", - "@jetbrains/icons": "3.6.0", - "@jetbrains/logos": "1.1.5", - "@jetbrains/ring-ui": "2.1.16", - "babel-loader": "^8.0.6", - "copy-webpack-plugin": "^5.1.1", - "css-loader": "^3.4.2", - "json-loader": "^0.5.7", - "postcss-import": "^12.0.1", - "postcss-loader": "^3.0.0", - "postcss-preset-env": "^6.7.0", - "prop-types": "^15.7.2", - "react": "^16.12.0", - "react-dom": "^16.12.0", - "redbox-react": "^1.6.0", - "webpack": "^4.41.5", - "webpack-cli": "^3.3.10", - "webpack-dev-server": "^3.10.1" - }, - "devDependencies": { - "@jetbrains/generator-ring-ui": "2.0.53", - "@jetbrains/eslint-config": "^4.0.6", - "@jetbrains/stylelint-config": "^2.0.0", - "babel-eslint": "^10.0.3", - "chai": "^4.2.0", - "chai-as-promised": "^7.1.1", - "chai-dom": "^1.8.1", - "chai-enzyme": "1.0.0-beta.1", - "electron": "^7.1.9", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.2", - "eslint": "^6.8.0", - "karma": "^4.4.1", - "karma-chai-plugins": "^0.9.0", - "karma-electron": "^6.3.0", - "karma-mocha": "^1.3.0", - "karma-sourcemap-loader": "^0.3.7", - "karma-teamcity-reporter": "^1.1.0", - "karma-webpack": "^4.0.2", - "mocha": "^6.2.2", - "react-test-renderer": "^16.12.0", - "sinon": "^8.0.4", - "sinon-chai": "^3.4.0", - "stylelint": "^12.0.1", - "xvfb-maybe": "^0.2.1", - "yo": "^3.1.1" - }, - "engines": { - "node": ">=8.0.0" - } -} diff --git a/core/search-component/postcss.config.js b/core/search-component/postcss.config.js deleted file mode 100644 index ce65774e..00000000 --- a/core/search-component/postcss.config.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = () => ({ - plugins: [ - require('postcss-import'), - require('postcss-preset-env')({ - features: { - stage: 3, // See https://cssdb.org/#staging-process - importFrom: require.resolve('@jetbrains/ring-ui/components/global/variables.css'), - features: { - 'nesting-rules': true, - 'custom-properties': { - preserve: true - } - } - } - }) - ] -}); diff --git a/core/search-component/src/main/js/search/app-root.js b/core/search-component/src/main/js/search/app-root.js deleted file mode 100644 index 5d650581..00000000 --- a/core/search-component/src/main/js/search/app-root.js +++ /dev/null @@ -1,47 +0,0 @@ -import React, {useRef, useState, useEffect} from 'react'; -import {WithFuzzySearchFilter} from './search'; -import './app.css'; - -function useComponentVisible(initialIsVisible) { - const [isComponentVisible, setIsComponentVisible] = useState(initialIsVisible); - const ref = useRef(null); - - const handleHideDropdown = (event) => { - if (event.key === "Escape") { - setIsComponentVisible(false); - } - }; - - const handleClickOutside = event => { - if (ref.current && !ref.current.contains(event.target)) { - setIsComponentVisible(false); - } - }; - - useEffect(() => { - document.addEventListener("keydown", handleHideDropdown, true); - document.addEventListener("click", handleClickOutside, true); - return () => { - document.removeEventListener("keydown", handleHideDropdown, true); - document.removeEventListener("click", handleClickOutside, true); - }; - }); - - return { ref, isComponentVisible, setIsComponentVisible }; -} - -export const AppRoot = () => { - const { - ref, - isComponentVisible, - setIsComponentVisible - } = useComponentVisible(false); - - return
- {isComponentVisible && ()} - {!isComponentVisible && ( - setIsComponentVisible(true)}> - - )} -
-} \ No newline at end of file diff --git a/core/search-component/src/main/js/search/app.css b/core/search-component/src/main/js/search/app.css deleted file mode 100644 index c51d3fe1..00000000 --- a/core/search-component/src/main/js/search/app.css +++ /dev/null @@ -1,21 +0,0 @@ -@import "@jetbrains/ring-ui/components/global/variables.css"; - -html, -.app-root { - height: 100%; -} - -.search-root { - margin: 0; - padding: 0; - - background: var(--ring-content-background-color); - - font-family: var(--ring-font-family); - font-size: var(--ring-font-size); - line-height: var(--ring-line-height); -} - -.search-content { - padding: 24px 41px; -} diff --git a/core/search-component/src/main/js/search/app.js b/core/search-component/src/main/js/search/app.js deleted file mode 100644 index bceffa48..00000000 --- a/core/search-component/src/main/js/search/app.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import {render} from 'react-dom'; -import RedBox from 'redbox-react'; - -import {AppRoot} from './app-root'; -import './app.css'; - -const appEl = document.getElementById('searchBar'); -const rootEl = document.createElement('div'); - -let renderApp = () => { - render( - , - rootEl - ); -}; - -if (module.hot) { - const renderAppHot = renderApp; - const renderError = error => { - render( - , - rootEl - ); - }; - - renderApp = () => { - try { - renderAppHot(); - } catch (error) { - renderError(error); - } - }; - - module.hot.accept('./app-root', () => { - setTimeout(renderApp); - }); -} - -renderApp(); -appEl.appendChild(rootEl); diff --git a/core/search-component/src/main/js/search/search.js b/core/search-component/src/main/js/search/search.js deleted file mode 100644 index a742f11d..00000000 --- a/core/search-component/src/main/js/search/search.js +++ /dev/null @@ -1,52 +0,0 @@ -import React, {Component} from 'react'; -import Select from '@jetbrains/ring-ui/components/select/select'; -import '@jetbrains/ring-ui/components/input-size/input-size.scss'; - -class WithFuzzySearchFilterComponent extends Component { - constructor(props) { - super(props); - this.state = {selected: props.data[0]}; - } - - clearSelection = () => { - this.setState({selected: null}); - }; - - onSelect = option => { - window.location.href = `${window.pathToRoot}${option.location}?query${option.name}`; - this.setState({selected: option}); - debugger - }; - - render() { - return ( -
-
- +
+
+ ); + } +} + +export const WithFuzzySearchFilter = () => { + let data = []; + if (window.pages) { + data = window.pages.map((page, i) => ({ + ...page, + label: page.name, + key: i + 1, + type: page.kind + })); + } + + return ; +}; diff --git a/plugins/base/search-component/stylelint.config.js b/plugins/base/search-component/stylelint.config.js new file mode 100644 index 00000000..02b3f4ac --- /dev/null +++ b/plugins/base/search-component/stylelint.config.js @@ -0,0 +1,4 @@ +module.exports = { + extends: '@jetbrains/stylelint-config', + rules: {} +}; diff --git a/plugins/base/search-component/webpack.config.js b/plugins/base/search-component/webpack.config.js new file mode 100644 index 00000000..a7b503b6 --- /dev/null +++ b/plugins/base/search-component/webpack.config.js @@ -0,0 +1,62 @@ +const {join, resolve} = require('path'); + +const ringUiWebpackConfig = require('@jetbrains/ring-ui/webpack.config'); + +const pkgConfig = require('./package.json').config; + +const componentsPath = join(__dirname, pkgConfig.components); + +// Patch @jetbrains/ring-ui svg-sprite-loader config +ringUiWebpackConfig.loaders.svgInlineLoader.include.push( + require('@jetbrains/logos'), + require('@jetbrains/icons') +); + +const webpackConfig = () => ({ + entry: `${componentsPath}/app.js`, + resolve: { + mainFields: ['module', 'browser', 'main'], + alias: { + react: resolve('./node_modules/react'), + 'react-dom': resolve('./node_modules/react-dom'), + '@jetbrains/ring-ui': resolve('./node_modules/@jetbrains/ring-ui') + } + }, + output: { + path: resolve(__dirname, pkgConfig.dist), + filename: '[name].js', + publicPath: '', + devtoolModuleFilenameTemplate: '/[absolute-resource-path]' + }, + module: { + rules: [ + ...ringUiWebpackConfig.config.module.rules, + { + test: /\.css$/, + include: componentsPath, + use: [ + 'style-loader', + {loader: 'css-loader'}, + {loader: 'postcss-loader'} + ] + }, + { + test: /\.css$/, + include: /node_modules/, + exclude: ringUiWebpackConfig.componentsPath, + use: ['style-loader', 'css-loader'] + }, + { + test: /\.js$/, + include: [componentsPath], + loader: 'babel-loader?cacheDirectory' + } + ] + }, + plugins: [], + output: { + path: __dirname + '/dist/' + } +}); + +module.exports = webpackConfig; diff --git a/plugins/base/src/main/kotlin/renderers/FileWriter.kt b/plugins/base/src/main/kotlin/renderers/FileWriter.kt index 236cbad8..181295c0 100644 --- a/plugins/base/src/main/kotlin/renderers/FileWriter.kt +++ b/plugins/base/src/main/kotlin/renderers/FileWriter.kt @@ -66,7 +66,7 @@ class FileWriter(val context: DokkaContext): OutputWriter { val filePath = file.toAbsolutePath().toString() withContext(Dispatchers.IO) { Paths.get(root, rebase(filePath)).toFile().writeBytes( - javaClass.getResourceAsStream(filePath).readBytes() + this@FileWriter.javaClass.getResourceAsStream(filePath).readBytes() ) } } diff --git a/plugins/base/src/main/resources/dokka/format/gfm.properties b/plugins/base/src/main/resources/dokka/format/gfm.properties new file mode 100644 index 00000000..5e8f7aa8 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/format/gfm.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.Formats.GFMFormatDescriptor +description=Produces documentation in GitHub-flavored markdown format diff --git a/plugins/base/src/main/resources/dokka/format/html-as-java.properties b/plugins/base/src/main/resources/dokka/format/html-as-java.properties new file mode 100644 index 00000000..f598f377 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/format/html-as-java.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.Formats.HtmlAsJavaFormatDescriptor +description=Produces output in HTML format using Java syntax \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/format/html.properties b/plugins/base/src/main/resources/dokka/format/html.properties new file mode 100644 index 00000000..7881dfae --- /dev/null +++ b/plugins/base/src/main/resources/dokka/format/html.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.Formats.HtmlFormatDescriptor +description=Produces output in HTML format \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/format/java-layout-html.properties b/plugins/base/src/main/resources/dokka/format/java-layout-html.properties new file mode 100644 index 00000000..fbb2bbed --- /dev/null +++ b/plugins/base/src/main/resources/dokka/format/java-layout-html.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.Formats.JavaLayoutHtmlFormatDescriptor +description=Produces Kotlin Style Docs with Javadoc like layout \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/format/jekyll.properties b/plugins/base/src/main/resources/dokka/format/jekyll.properties new file mode 100644 index 00000000..b11401a4 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/format/jekyll.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.Formats.JekyllFormatDescriptor +description=Produces documentation in Jekyll format \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/format/kotlin-website-html.properties b/plugins/base/src/main/resources/dokka/format/kotlin-website-html.properties new file mode 100644 index 00000000..f4c320b9 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/format/kotlin-website-html.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.Formats.KotlinWebsiteHtmlFormatDescriptor +description=Generates Kotlin website documentation \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/format/markdown.properties b/plugins/base/src/main/resources/dokka/format/markdown.properties new file mode 100644 index 00000000..6217a6df --- /dev/null +++ b/plugins/base/src/main/resources/dokka/format/markdown.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.Formats.MarkdownFormatDescriptor +description=Produces documentation in markdown format \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/images/arrow_down.svg b/plugins/base/src/main/resources/dokka/images/arrow_down.svg new file mode 100755 index 00000000..89e7df47 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/arrow_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/base/src/main/resources/dokka/images/docs_logo.svg b/plugins/base/src/main/resources/dokka/images/docs_logo.svg new file mode 100644 index 00000000..7c1e3ae8 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/docs_logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/logo-icon.svg b/plugins/base/src/main/resources/dokka/images/logo-icon.svg new file mode 100755 index 00000000..1b3b3670 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/logo-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/base/src/main/resources/dokka/images/logo-text.svg b/plugins/base/src/main/resources/dokka/images/logo-text.svg new file mode 100755 index 00000000..7bf3e6c5 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/logo-text.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/plugins/base/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties b/plugins/base/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties new file mode 100644 index 00000000..c484a920 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.InboundExternalLinkResolutionService$Dokka +description=Uses Dokka Default resolver \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties b/plugins/base/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties new file mode 100644 index 00000000..3b61eabe --- /dev/null +++ b/plugins/base/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.Formats.JavaLayoutHtmlInboundLinkResolutionService +description=Resolver for JavaLayoutHtml \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/inbound-link-resolver/javadoc.properties b/plugins/base/src/main/resources/dokka/inbound-link-resolver/javadoc.properties new file mode 100644 index 00000000..0d5d7d17 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/inbound-link-resolver/javadoc.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.InboundExternalLinkResolutionService$Javadoc +description=Uses Javadoc Default resolver \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/scripts/navigationLoader.js b/plugins/base/src/main/resources/dokka/scripts/navigationLoader.js new file mode 100644 index 00000000..cac46d2a --- /dev/null +++ b/plugins/base/src/main/resources/dokka/scripts/navigationLoader.js @@ -0,0 +1,43 @@ +window.addEventListener('load', () => { + fetch(pathToRoot + "navigation.html") + .then(response => response.text()) + .then(data => { + document.getElementById("sideMenu").innerHTML = data; + }).then(() => { + document.querySelectorAll(".overview > a").forEach(link => { + link.setAttribute("href", pathToRoot + link.getAttribute("href")); + }) + }).then(() => { + document.querySelectorAll(".sideMenuPart").forEach(nav => { + if (!nav.classList.contains("hidden")) nav.classList.add("hidden") + }) + }).then(() => { + revealNavigationForCurrentPage() + }) +}) + +revealNavigationForCurrentPage = () => { + let pageId = document.getElementById("content").attributes["pageIds"].value.toString(); + let parts = document.querySelectorAll(".sideMenuPart"); + let found = 0; + do { + parts.forEach(part => { + if (part.attributes['pageId'].value.indexOf(pageId) !== -1 && found === 0) { + found = 1; + if (part.classList.contains("hidden")){ + part.classList.remove("hidden"); + part.setAttribute('data-active',""); + } + revealParents(part) + } + }); + pageId = pageId.substring(0, pageId.lastIndexOf("/")) + } while (pageId.indexOf("/") !== -1 && found === 0) +}; + +revealParents = (part) => { + if (part.classList.contains("sideMenuPart")) { + if (part.classList.contains("hidden")) part.classList.remove("hidden"); + revealParents(part.parentNode) + } +}; \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/scripts/platformContentHandler.js b/plugins/base/src/main/resources/dokka/scripts/platformContentHandler.js new file mode 100644 index 00000000..72c8daae --- /dev/null +++ b/plugins/base/src/main/resources/dokka/scripts/platformContentHandler.js @@ -0,0 +1,52 @@ +window.addEventListener('load', () => { + document.querySelectorAll("div[data-platform-hinted]") + .forEach(elem => elem.addEventListener('click', (event) => togglePlatformDependent(event,elem))) + document.querySelectorAll("div[tabs-section]") + .forEach(elem => elem.addEventListener('click', (event) => toggleSections(event))) + document.querySelector(".tabs-section-body") + .querySelector("div[data-togglable]") + .setAttribute("data-active", "") +}) + +function toggleSections(evt){ + if(!evt.target.getAttribute("data-togglable")) return + + const activateTabs = (containerClass) => { + for(const element of document.getElementsByClassName(containerClass)){ + for(const child of element.children){ + if(child.getAttribute("data-togglable") === evt.target.getAttribute("data-togglable")){ + child.setAttribute("data-active", "") + } else { + child.removeAttribute("data-active") + } + } + } + } + + activateTabs("tabs-section") + activateTabs("tabs-section-body") +} + +function togglePlatformDependent(e, container) { + let target = e.target + if (target.tagName != 'BUTTON') return; + let index = target.getAttribute('data-toggle') + + for(let child of container.children){ + if(child.hasAttribute('data-toggle-list')){ + for(let bm of child.children){ + if(bm == target){ + bm.setAttribute('data-active',"") + } else if(bm != target) { + bm.removeAttribute('data-active') + } + } + } + else if(child.getAttribute('data-togglable') == index) { + child.setAttribute('data-active',"") + } + else { + child.removeAttribute('data-active') + } + } +} diff --git a/plugins/base/src/main/resources/dokka/scripts/scripts.js b/plugins/base/src/main/resources/dokka/scripts/scripts.js new file mode 100644 index 00000000..c2e29b9f --- /dev/null +++ b/plugins/base/src/main/resources/dokka/scripts/scripts.js @@ -0,0 +1,11 @@ +document.getElementById("navigationFilter").oninput = function (e) { + var input = e.target.value; + var menuParts = document.getElementsByClassName("sideMenuPart") + for (let part of menuParts) { + if(part.querySelector("a").textContent.startsWith(input)) { + part.classList.remove("filtered"); + } else { + part.classList.add("filtered"); + } + } +} \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/scripts/search.js b/plugins/base/src/main/resources/dokka/scripts/search.js new file mode 100644 index 00000000..04d88ab5 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/scripts/search.js @@ -0,0 +1,7 @@ +let query = new URLSearchParams(window.location.search).get("query"); +document.getElementById("searchTitle").innerHTML += '"' + query + '":'; +document.getElementById("searchTable").innerHTML = pages + .filter(el => el.name.toLowerCase().startsWith(query.toLowerCase())) + .reduce((acc, element) => { + return acc + '' + element.name + '' + }, ""); \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/styles/jetbrains-mono.css b/plugins/base/src/main/resources/dokka/styles/jetbrains-mono.css new file mode 100644 index 00000000..2af32a92 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/styles/jetbrains-mono.css @@ -0,0 +1,13 @@ +@font-face{ + font-family: 'JetBrains Mono'; + src: url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/web/woff2/JetBrainsMono-Regular.woff2') format('woff2'); + font-weight: normal; + font-style: normal; +} + +@font-face{ + font-family: 'JetBrains Mono'; + src: url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/web/woff2/JetBrainsMono-Bold.woff2') format('woff2'); + font-weight: bold; + font-style: normal; +} \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/styles/style.css b/plugins/base/src/main/resources/dokka/styles/style.css new file mode 100644 index 00000000..6ba41db4 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/styles/style.css @@ -0,0 +1,735 @@ +@import url(https://fonts.googleapis.com/css?family=Open+Sans:300i,400,700); +@import url('https://rsms.me/inter/inter.css'); +@import url('jetbrains-mono.css'); + +:root { + --breadcrumb-font-color: #A6AFBA; + --hover-link-color: #5B5DEF +} + +#content { + padding: 0 41px; +} + +.breadcrumbs { + padding: 24px 0; + color: var(--breadcrumb-font-color); +} + +.breadcrumbs a { + color: var(--breadcrumb-font-color) +} + +.breadcrumbs a:hover { + color: var(--hover-link-color) +} + +.tabs-section-body > *:not([data-active]){ + display: none; +} + +.tabs-section > .section-tab:first-child { + margin-left: 0; +} + +.section-tab { + border: 0; + cursor: pointer; + background-color: transparent; + border-bottom: 1px solid #DADFE6; + padding: 11px 3px; + font-size: 14px; + color: #637282; + outline:none; + margin: 0 8px; +} + +.section-tab:hover { + color: #282E34; + border-bottom: 2px solid var(--hover-link-color); +} + +.section-tab[data-active=''] { + color: #282E34; + border-bottom: 2px solid var(--hover-link-color); +} + +.tabs-section-body { + padding: 24px 0 +} + +.cover > .platform-hinted { + padding: 24px 0 +} + +.tabbedcontent { + padding: 24px 0; +} + +.cover .platform-hinted .single-content > .symbol { + background-color: white; +} + +.divergent-group { + background-color: white; + padding: 12px 8px; + margin-bottom: 2px; +} + +.divergent-group .table-row { + background-color: #F4F4F4; +} + +#container { + display: flex; + flex-direction: row; + min-height: 100%; +} + +#main { + width: 100%; + padding-left: 12px; +} + +#leftColumn { + width: 280px; + min-height: 100%; + border-right: 1px solid #DADFE6; + flex: 0 0 auto; +} + +@media screen and (max-width: 600px) { + #container { + flex-direction: column; + } + + #leftColumn { + border-right: none; + } +} + +#sideMenu { + height: 100%; + padding-top: 16px; + position: relative; + overflow: auto; +} + +#sideMenu img { + margin: 1em 0.25em; +} + +#sideMenu hr { + background: #DADFE6; +} + +#searchBar { + float: right; +} + +#logo { + background-size: 125px 26px; + border-bottom: 1px solid #DADFE6; + background-repeat: no-repeat; + background-image: url(../images/docs_logo.svg); + background-origin: content-box; + padding-left: 24px; + padding-top: 24px; + height: 48px; +} + +.monospace, +.code { + font-family: monospace; +} + +.overview > .navButton { + width: 100%; + height: 100%; + align-items: center; + display: flex; + justify-content: flex-end; + padding-right: 24px; +} + +.strikethrough { + text-decoration: line-through; +} + +.symbol:empty { + padding: 0; +} + +.symbol { + background-color: #F4F4F4; + align-items: center; + display: flex; + padding: 8px 16px; + box-sizing: border-box; + font-weight: bold; + white-space: pre; + flex-wrap: wrap; +} + +#nav-submenu > .sideMenuPart { + padding-left: 0; /* packages are same level as modules */ +} + +.sideMenuPart > .overview { + height: 40px; + width: 100%; + display: flex; + align-items: center; + position: relative; + user-select: none; /* there's a weird bug with text selection */ +} + +.sideMenuPart a { + display: flex; + align-items: center; + flex: 1; + height: 100%; + color: #637282; +} + +.sideMenuPart > .overview:before { + box-sizing: border-box; + content: ''; + top: 0; + width: 200%; + right: 0; + bottom: 0; + position: absolute; + z-index: -1; +} + +.overview:hover:before { + background-color: #DADFE5; +} + +#nav-submenu { + padding-left: 24px; +} + +.sideMenuPart { + padding-left: 12px; + box-sizing: border-box; +} + +.sideMenuPart .hidden > .overview .navButtonContent::before { + transform: rotate(0deg); +} + +.sideMenuPart > .overview .navButtonContent::before { + content: url("../images/arrow_down.svg"); + height: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + transform: rotate(180deg); +} + +.sideMenuPart.hidden > .navButton .navButtonContent::after { + content: '\02192'; +} + +.sideMenuPart.hidden > .sideMenuPart { + height: 0; + visibility: hidden; +} + +.filtered > a, .filtered > .navButton { + display: none; +} + +body, table { + font-family: 'Inter', "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + background: #F4F4F4; + font-style: normal; + font-weight: normal; + font-size: 14px; + line-height: 24px; + margin: 0; + max-width: 1440px; +} + +table { + width: 100%; + border-collapse: collapse; + background-color: #ffffff; + padding: 5px; +} + +tbody > tr { + border-bottom: 2px solid #F4F4F4; + min-height: 56px; +} + +td:first-child { + width: 20vw; +} + +.keyword { + color: black; + font-family: JetBrains Mono, Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; + font-size: 12px; +} + +.symbol { + font-family: JetBrains Mono, Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; + font-size: 12px; + min-height: 43px; +} + +.symbol > a { + color: var(--hover-link-color); + text-decoration: underline; +} + +.identifier { + color: darkblue; + font-size: 12px; + font-family: JetBrains Mono, Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; +} + +.brief { + white-space: pre-wrap; + overflow: hidden; + padding-top: 8px; +} + +h1, h2, h3, h4, h5, h6 { + color: #222; +} + +p, ul, ol, table, pre, dl { + margin: 0 0 20px; +} + +h1, h2, h3 { + line-height: 1.1; +} + +h1 { + font-size: 60px; + line-height: 64px; + letter-spacing: -1.5px; +} + +h2 { + color: #393939; +} + +h3, h4, h5, h6 { + color: #494949; +} + +a { + color: #258aaf; + font-weight: 400; + text-decoration: none; +} + +a:hover { + color: #282E34; + text-decoration: none; +} + +a small { + font-size: 11px; + color: #555; + margin-top: -0.6em; + display: block; +} + +.wrapper { + width: 860px; + margin: 0 auto; +} + +blockquote { + border-left: 1px solid #e5e5e5; + margin: 0; + padding: 0 0 0 20px; + font-style: italic; +} + +code, pre { + font-family: Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; + color: #333; + font-size: 12px; +} + +pre { + display: block; + /* + padding:8px 8px; + background: #f8f8f8; + border-radius:5px; + border:1px solid #e5e5e5; + */ + overflow-x: auto; +} + +th, td { + text-align: left; + vertical-align: top; + padding: 5px 10px; +} + +dt { + color: #444; + font-weight: 700; +} + +th { + color: #444; +} + +img { + max-width: 100%; +} + +header { + width: 270px; + float: left; + position: fixed; +} + +header ul { + list-style: none; + height: 40px; + + padding: 0; + + background: #eee; + background: -moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #f8f8f8), color-stop(100%, #dddddd)); + background: -webkit-linear-gradient(top, #f8f8f8 0%, #dddddd 100%); + background: -o-linear-gradient(top, #f8f8f8 0%, #dddddd 100%); + background: -ms-linear-gradient(top, #f8f8f8 0%, #dddddd 100%); + background: linear-gradient(top, #f8f8f8 0%, #dddddd 100%); + + border-radius: 5px; + border: 1px solid #d2d2d2; + box-shadow: inset #fff 0 1px 0, inset rgba(0, 0, 0, 0.03) 0 -1px 0; + width: 270px; +} + +header li { + width: 89px; + float: left; + border-right: 1px solid #d2d2d2; + height: 40px; +} + +header ul a { + line-height: 1; + font-size: 11px; + color: #999; + display: block; + text-align: center; + padding-top: 6px; + height: 40px; +} + +strong { + color: #222; + font-weight: 700; +} + +header ul li + li { + width: 88px; + border-left: 1px solid #fff; +} + +header ul li + li + li { + border-right: none; + width: 89px; +} + +header ul a strong { + font-size: 14px; + display: block; + color: #222; +} + +section { + width: 500px; + float: right; + padding-bottom: 50px; +} + +small { + font-size: 11px; +} + +hr { + border: 0; + background: #e5e5e5; + height: 1px; + margin: 0 0 20px; +} + +footer { + width: 270px; + float: left; + position: fixed; + bottom: 50px; +} + +.platform-tag { + display: flex; + flex-direction: row; + padding: 4px 8px; + height: 16px; + border-radius: 100px; + + font-family: Inter, Arial, sans-serif; + font-size: 12px; + font-weight: 400; + font-style: normal; + font-stretch: normal; + line-height: normal; + letter-spacing: normal; + text-align: center; + + color: #fff + +} + +.platform-tags { + flex: 0 0 auto; + display: flex; +} + +.platform-tags > .platform-tag { + align-self: center; +} + +.platform-tag.jvm-like { + background-color: #4DBB5F; + color: white; +} + +.platform-tag.js-like { + background-color: #FED236; + color: white; +} + +.platform-tag.native-like { + background-color: #CD74F6; + color: white; +} + +.platform-tag.common-like { + background-color: #A6AFBA; + color: white; +} + +td.content { + padding-left: 24px; + padding-top: 16px; + display: flex; + flex-direction: column; +} + +.main-subrow { + display: flex; + flex-direction: row; + padding: 0; + justify-content: space-between; +} + +.main-subrow > a { + text-decoration: none; + font-style: normal; + font-weight: 600; + font-size: 14px; + color: #282E34; +} + +.main-subrow > a:hover { + color: var(--hover-link-color); +} + +.platform-hinted { + flex: auto; + display: block; + margin-bottom: 5px; +} + +.platform-hinted > .platform-bookmarks-row > .platform-bookmark { + min-width: 64px; + height: 36px; + border: 2px solid white; + background: white; + outline: none; + flex: none; + order: 5; + align-self: flex-start; + margin: 0; +} + +.platform-hinted > .platform-bookmarks-row > .platform-bookmark.jvm-like:hover { + border-top: 2px solid rgba(77, 187, 95, 0.3); +} + +.platform-hinted > .platform-bookmarks-row > .platform-bookmark.js-like:hover { + border-top: 2px solid rgba(254, 175, 54, 0.3); +} + +.platform-hinted > .platform-bookmarks-row > .platform-bookmark.native-like:hover { + border-top: 2px solid rgba(105, 118, 249, 0.3); +} + +.platform-hinted > .platform-bookmarks-row > .platform-bookmark.common-like:hover { + border-top: 2px solid rgba(161, 170, 180, 0.3); +} + +.platform-hinted > .platform-bookmarks-row > .platform-bookmark.jvm-like[data-active=''] { + border: 2px solid #F4F4F4; + border-top: 2px solid #4DBB5F; + + background: #F4F4F4; +} + +.platform-hinted > .platform-bookmarks-row > .platform-bookmark.js-like[data-active=''] { + border: 2px solid #F4F4F4; + border-top: 2px solid #FED236; + + background: #F4F4F4; +} + +.platform-hinted > .platform-bookmarks-row > .platform-bookmark.native-like[data-active=''] { + border: 2px solid #F4F4F4; + border-top: 2px solid #CD74F6; + + background: #F4F4F4; +} + +.platform-hinted > .platform-bookmarks-row > .platform-bookmark.common-like[data-active=''] { + border: 2px solid #F4F4F4; + border-top: 2px solid #A6AFBA; + + background: #F4F4F4; +} + +.platform-hinted > .content:not([data-active]) { + display: none +} + +.sideMenuPart[data-active] > .overview { + background: rgba(91, 93, 239, 0.15); + border-left: 4px solid var(--hover-link-color); +} + +.table { + display: flex; + flex-direction: column; +} + +.table-row { + display: flex; + flex-direction: column; + background: white; + margin-bottom: 2px; + padding: 16px 24px 16px 24px; +} + +.platform-dependent-row { + display: grid; + padding-top: 8px; +} + +.title-row { + display: grid; + grid-template-columns: auto auto 7em; + width: 100%; +} + +.keyValue { + display: grid; +} + +@media print, screen and (min-width: 960px) { + .keyValue { + grid-template-columns: 20% auto; + } + + .title-row { + grid-template-columns: 20% auto 7em; + } +} + +@media print, screen and (max-width: 960px) { + + div.wrapper { + width: auto; + margin: 0; + } + + header, section, footer { + float: none; + position: static; + width: auto; + } + + header { + padding-right: 320px; + } + + section { + border: 1px solid #e5e5e5; + border-width: 1px 0; + padding: 20px 0; + margin: 0 0 20px; + } + + header a small { + display: inline; + } + + header ul { + position: absolute; + right: 50px; + top: 52px; + } +} + +@media print, screen and (max-width: 720px) { + body { + word-wrap: break-word; + } + + header { + padding: 0; + } + + header ul, header p.view { + position: static; + } + + pre, code { + word-wrap: normal; + } +} + +@media print, screen and (max-width: 480px) { + body { + padding-right: 15px; + } + + header ul { + display: none; + } +} + +@media print { + body { + padding: 0.4in; + font-size: 12pt; + color: #444; + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 82bc8f5d..95ad24b0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,3 +32,5 @@ pluginManagement { gradlePluginPortal() } } +include("plugins:base:search-component") +findProject(":plugins:base:search-component")?.name = "search-component" -- cgit From b614604effda51ca7c76c8901be78ced62b642b2 Mon Sep 17 00:00:00 2001 From: Marcin Aman Date: Thu, 28 May 2020 16:17:13 +0200 Subject: Update TS migration to current dev, move to a common package, rename to frontend --- .../src/main/components/app/index.scss | 21 ------- .../src/main/components/app/index.tsx | 11 ---- core/search-component/src/main/components/root.tsx | 43 --------------- .../src/main/components/search/search.tsx | 45 --------------- .../src/main/components/search/types.ts | 26 --------- core/search-component/src/main/scss/index.scss | 1 - core/search-component/tsconfig.json | 34 ------------ core/search-component/types/@jetbrains/index.d.ts | 3 - core/search-component/types/index.d.ts | 1 - plugins/base/build.gradle.kts | 8 +-- plugins/base/frontend/.gitignore | 3 + plugins/base/frontend/build.gradle.kts | 18 ++++++ plugins/base/frontend/package.json | 57 +++++++++++++++++++ plugins/base/frontend/postcss.config.js | 16 ++++++ .../frontend/src/main/components/app/index.scss | 21 +++++++ .../frontend/src/main/components/app/index.tsx | 50 +++++++++++++++++ plugins/base/frontend/src/main/components/root.tsx | 43 +++++++++++++++ .../frontend/src/main/components/search/search.tsx | 45 +++++++++++++++ .../frontend/src/main/components/search/types.ts | 26 +++++++++ plugins/base/frontend/src/main/scss/index.scss | 1 + .../frontend/src/main/types/@jetbrains/index.d.ts | 3 + plugins/base/frontend/stylelint.config.js | 4 ++ plugins/base/frontend/tsconfig.json | 30 ++++++++++ plugins/base/frontend/webpack.config.js | 64 ++++++++++++++++++++++ plugins/base/search-component/.gitignore | 3 - plugins/base/search-component/build.gradle.kts | 18 ------ plugins/base/search-component/package.json | 60 -------------------- plugins/base/search-component/postcss.config.js | 16 ------ .../src/main/js/search/app-root.js | 47 ---------------- plugins/base/search-component/stylelint.config.js | 4 -- plugins/base/search-component/webpack.config.js | 64 ---------------------- settings.gradle.kts | 4 +- 32 files changed, 388 insertions(+), 402 deletions(-) delete mode 100644 core/search-component/src/main/components/app/index.scss delete mode 100644 core/search-component/src/main/components/app/index.tsx delete mode 100644 core/search-component/src/main/components/root.tsx delete mode 100644 core/search-component/src/main/components/search/search.tsx delete mode 100644 core/search-component/src/main/components/search/types.ts delete mode 100644 core/search-component/src/main/scss/index.scss delete mode 100644 core/search-component/tsconfig.json delete mode 100644 core/search-component/types/@jetbrains/index.d.ts delete mode 100644 core/search-component/types/index.d.ts create mode 100644 plugins/base/frontend/.gitignore create mode 100644 plugins/base/frontend/build.gradle.kts create mode 100644 plugins/base/frontend/package.json create mode 100644 plugins/base/frontend/postcss.config.js create mode 100644 plugins/base/frontend/src/main/components/app/index.scss create mode 100644 plugins/base/frontend/src/main/components/app/index.tsx create mode 100644 plugins/base/frontend/src/main/components/root.tsx create mode 100644 plugins/base/frontend/src/main/components/search/search.tsx create mode 100644 plugins/base/frontend/src/main/components/search/types.ts create mode 100644 plugins/base/frontend/src/main/scss/index.scss create mode 100644 plugins/base/frontend/src/main/types/@jetbrains/index.d.ts create mode 100644 plugins/base/frontend/stylelint.config.js create mode 100644 plugins/base/frontend/tsconfig.json create mode 100644 plugins/base/frontend/webpack.config.js delete mode 100644 plugins/base/search-component/.gitignore delete mode 100644 plugins/base/search-component/build.gradle.kts delete mode 100644 plugins/base/search-component/package.json delete mode 100644 plugins/base/search-component/postcss.config.js delete mode 100644 plugins/base/search-component/src/main/js/search/app-root.js delete mode 100644 plugins/base/search-component/stylelint.config.js delete mode 100644 plugins/base/search-component/webpack.config.js (limited to 'plugins/base/build.gradle.kts') diff --git a/core/search-component/src/main/components/app/index.scss b/core/search-component/src/main/components/app/index.scss deleted file mode 100644 index a7406115..00000000 --- a/core/search-component/src/main/components/app/index.scss +++ /dev/null @@ -1,21 +0,0 @@ -@import "src/main/scss/index"; - -html, -.app-root { - height: 100%; -} - -.search-root { - margin: 0; - padding: 0; - - background: var(--ring-content-background-color); - - font-family: var(--ring-font-family); - font-size: var(--ring-font-size); - line-height: var(--ring-line-height); -} - -.search-content { - margin: calc(var(--ring-unit) * 4); -} diff --git a/core/search-component/src/main/components/app/index.tsx b/core/search-component/src/main/components/app/index.tsx deleted file mode 100644 index 3427c1ce..00000000 --- a/core/search-component/src/main/components/app/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import {WithFuzzySearchFilter} from '../search/search'; -import './index.scss'; - -const App: React.FC = () => ( -
- -
-) - -export default App diff --git a/core/search-component/src/main/components/root.tsx b/core/search-component/src/main/components/root.tsx deleted file mode 100644 index 70ed9550..00000000 --- a/core/search-component/src/main/components/root.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import {render} from 'react-dom'; -import RedBox from 'redbox-react'; - -import App from "./app"; -import './app/index.scss'; - -const appEl = document.getElementById('searchBar'); -const rootEl = document.createElement('div'); - -let renderApp = () => { - render( - , - rootEl - ); -}; - -// @ts-ignore -if (module.hot) { - const renderAppHot = renderApp; - const renderError = (error: Error) => { - render( - , - rootEl - ); - }; - - renderApp = () => { - try { - renderAppHot(); - } catch (error) { - renderError(error); - } - }; - - // @ts-ignore - module.hot.accept('./app', () => { - setTimeout(renderApp); - }); -} - -renderApp(); -appEl!.appendChild(rootEl); diff --git a/core/search-component/src/main/components/search/search.tsx b/core/search-component/src/main/components/search/search.tsx deleted file mode 100644 index f0df0c98..00000000 --- a/core/search-component/src/main/components/search/search.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, {useCallback, useState} from 'react'; -import {Select} from '@jetbrains/ring-ui'; -import '@jetbrains/ring-ui/components/input-size/input-size.scss'; -import {IWindow, Option, Props, State} from "./types"; - -const WithFuzzySearchFilterComponent: React.FC = ({data}: Props) => { - const [selected, onSelected] = useState