From b536699655e40c62cd603e1f98869786566604bd Mon Sep 17 00:00:00 2001 From: Kamil Doległo Date: Tue, 10 Sep 2019 15:15:09 +0200 Subject: WIP on restructuring --- .../javadoc8/src/main/kotlin/javadoc/docbase.kt | 539 +++++++++++++++++++++ .../src/main/kotlin/javadoc/dokka-adapters.kt | 42 ++ .../javadoc8/src/main/kotlin/javadoc/reporter.kt | 34 ++ .../src/main/kotlin/javadoc/source-position.kt | 19 + plugins/javadoc8/src/main/kotlin/javadoc/tags.kt | 228 +++++++++ .../main/resources/dokka/format/javadoc.properties | 2 + .../src/test/kotlin/javadoc/JavadocTest.kt | 333 +++++++++++++ 7 files changed, 1197 insertions(+) create mode 100644 plugins/javadoc8/src/main/kotlin/javadoc/docbase.kt create mode 100644 plugins/javadoc8/src/main/kotlin/javadoc/dokka-adapters.kt create mode 100644 plugins/javadoc8/src/main/kotlin/javadoc/reporter.kt create mode 100644 plugins/javadoc8/src/main/kotlin/javadoc/source-position.kt create mode 100644 plugins/javadoc8/src/main/kotlin/javadoc/tags.kt create mode 100644 plugins/javadoc8/src/main/resources/dokka/format/javadoc.properties create mode 100644 plugins/javadoc8/src/test/kotlin/javadoc/JavadocTest.kt (limited to 'plugins/javadoc8/src') diff --git a/plugins/javadoc8/src/main/kotlin/javadoc/docbase.kt b/plugins/javadoc8/src/main/kotlin/javadoc/docbase.kt new file mode 100644 index 00000000..0bf72ccf --- /dev/null +++ b/plugins/javadoc8/src/main/kotlin/javadoc/docbase.kt @@ -0,0 +1,539 @@ +package org.jetbrains.dokka.javadoc + +import com.sun.javadoc.* +import org.jetbrains.dokka.* +import java.lang.reflect.Modifier.* +import java.util.* +import kotlin.reflect.KClass + +private interface HasModule { + val module: ModuleNodeAdapter +} + +private interface HasDocumentationNode { + val node: DocumentationNode +} + +open class DocumentationNodeBareAdapter(override val node: DocumentationNode) : Doc, HasDocumentationNode { + private var rawCommentText_: String? = null + + override fun name(): String = node.name + override fun position(): SourcePosition? = SourcePositionAdapter(node) + + override fun inlineTags(): Array? = emptyArray() + override fun firstSentenceTags(): Array? = emptyArray() + override fun tags(): Array = emptyArray() + override fun tags(tagname: String?): Array? = tags().filter { it.kind() == tagname || it.kind() == "@$tagname" }.toTypedArray() + override fun seeTags(): Array? = tags().filterIsInstance().toTypedArray() + override fun commentText(): String = "" + + override fun setRawCommentText(rawDocumentation: String?) { + rawCommentText_ = rawDocumentation ?: "" + } + + override fun getRawCommentText(): String = rawCommentText_ ?: "" + + override fun isError(): Boolean = false + override fun isException(): Boolean = node.kind == NodeKind.Exception + override fun isEnumConstant(): Boolean = node.kind == NodeKind.EnumItem + override fun isEnum(): Boolean = node.kind == NodeKind.Enum + override fun isMethod(): Boolean = node.kind == NodeKind.Function + override fun isInterface(): Boolean = node.kind == NodeKind.Interface + override fun isField(): Boolean = node.kind == NodeKind.Field + override fun isClass(): Boolean = node.kind == NodeKind.Class + override fun isAnnotationType(): Boolean = node.kind == NodeKind.AnnotationClass + override fun isConstructor(): Boolean = node.kind == NodeKind.Constructor + override fun isOrdinaryClass(): Boolean = node.kind == NodeKind.Class + override fun isAnnotationTypeElement(): Boolean = node.kind == NodeKind.Annotation + + override fun compareTo(other: Any?): Int = when (other) { + !is DocumentationNodeAdapter -> 1 + else -> node.name.compareTo(other.node.name) + } + + override fun equals(other: Any?): Boolean = node.qualifiedName() == (other as? DocumentationNodeAdapter)?.node?.qualifiedName() + override fun hashCode(): Int = node.name.hashCode() + + override fun isIncluded(): Boolean = node.kind != NodeKind.ExternalClass +} + + +// TODO think of source position instead of null +// TODO tags +open class DocumentationNodeAdapter(override val module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeBareAdapter(node), HasModule { + override fun inlineTags(): Array = buildInlineTags(module, this, node.content).toTypedArray() + override fun firstSentenceTags(): Array = buildInlineTags(module, this, node.summary).toTypedArray() + + override fun tags(): Array { + val result = ArrayList(buildInlineTags(module, this, node.content)) + node.content.sections.flatMapTo(result) { + when (it.tag) { + ContentTags.SeeAlso -> buildInlineTags(module, this, it) + else -> emptyList() + } + } + + node.deprecation?.let { + val content = it.content.asText() + result.add(TagImpl(this, "deprecated", content ?: "")) + } + + return result.toTypedArray() + } +} + +// should be extension property but can't because of KT-8745 +private fun nodeAnnotations(self: T): List where T : HasModule, T : HasDocumentationNode + = self.node.annotations.map { AnnotationDescAdapter(self.module, it) } + +private fun DocumentationNode.hasAnnotation(klass: KClass<*>) = klass.qualifiedName in annotations.map { it.qualifiedName() } +private fun DocumentationNode.hasModifier(name: String) = details(NodeKind.Modifier).any { it.name == name } + + +class PackageAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), PackageDoc { + private val allClasses = listOf(node).collectAllTypesRecursively() + + override fun findClass(className: String?): ClassDoc? = + allClasses.get(className)?.let { ClassDocumentationNodeAdapter(module, it) } + + override fun annotationTypes(): Array = emptyArray() + override fun annotations(): Array = node.members(NodeKind.AnnotationClass).map { AnnotationDescAdapter(module, it) }.toTypedArray() + override fun exceptions(): Array = node.members(NodeKind.Exception).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray() + override fun ordinaryClasses(): Array = node.members(NodeKind.Class).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray() + override fun interfaces(): Array = node.members(NodeKind.Interface).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray() + override fun errors(): Array = emptyArray() + override fun enums(): Array = node.members(NodeKind.Enum).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray() + override fun allClasses(filter: Boolean): Array = allClasses.values.map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray() + override fun allClasses(): Array = allClasses(true) + + override fun isIncluded(): Boolean = node.name in module.allPackages +} + +class AnnotationTypeDocAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : ClassDocumentationNodeAdapter(module, node), AnnotationTypeDoc { + override fun elements(): Array? = emptyArray() // TODO +} + +class AnnotationDescAdapter(val module: ModuleNodeAdapter, val node: DocumentationNode) : AnnotationDesc { + override fun annotationType(): AnnotationTypeDoc? = AnnotationTypeDocAdapter(module, node.links.find { it.kind == NodeKind.AnnotationClass } ?: node) // TODO ????? + override fun isSynthesized(): Boolean = false + override fun elementValues(): Array? = emptyArray() // TODO +} + +open class ProgramElementAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), ProgramElementDoc { + override fun isPublic(): Boolean = node.hasModifier("public") || node.hasModifier("internal") + override fun isPackagePrivate(): Boolean = false + override fun isStatic(): Boolean = node.hasModifier("static") + override fun modifierSpecifier(): Int = visibilityModifier or (if (isStatic) STATIC else 0) + private val visibilityModifier + get() = when { + isPublic -> PUBLIC + isPrivate -> PRIVATE + isProtected -> PROTECTED + else -> 0 + } + override fun qualifiedName(): String? = node.qualifiedName() + override fun annotations(): Array? = nodeAnnotations(this).toTypedArray() + override fun modifiers(): String? = "public ${if (isStatic) "static" else ""}".trim() + override fun isProtected(): Boolean = node.hasModifier("protected") + + override fun isFinal(): Boolean = node.hasModifier("final") + + override fun containingPackage(): PackageDoc? { + if (node.kind == NodeKind.Type) { + return null + } + + var owner: DocumentationNode? = node + while (owner != null) { + if (owner.kind == NodeKind.Package) { + return PackageAdapter(module, owner) + } + owner = owner.owner + } + + return null + } + + override fun containingClass(): ClassDoc? { + if (node.kind == NodeKind.Type) { + return null + } + + var owner = node.owner + while (owner != null) { + if (owner.kind in NodeKind.classLike) { + return ClassDocumentationNodeAdapter(module, owner) + } + owner = owner.owner + } + + return null + } + + override fun isPrivate(): Boolean = node.hasModifier("private") + override fun isIncluded(): Boolean = containingPackage()?.isIncluded ?: false && containingClass()?.let { it.isIncluded } ?: true +} + +open class TypeAdapter(override val module: ModuleNodeAdapter, override val node: DocumentationNode) : Type, HasDocumentationNode, HasModule { + private val javaLanguageService = JavaLanguageService() + + override fun qualifiedTypeName(): String = javaLanguageService.getArrayElementType(node)?.qualifiedNameFromType() ?: node.qualifiedNameFromType() + override fun typeName(): String = (javaLanguageService.getArrayElementType(node)?.simpleName() ?: node.simpleName()) + dimension() + override fun simpleTypeName(): String = typeName() // TODO difference typeName() vs simpleTypeName() + + override fun dimension(): String = Collections.nCopies(javaLanguageService.getArrayDimension(node), "[]").joinToString("") + override fun isPrimitive(): Boolean = simpleTypeName() in setOf("int", "long", "short", "byte", "char", "double", "float", "boolean", "void") + + override fun asClassDoc(): ClassDoc? = if (isPrimitive) null else + elementType?.asClassDoc() ?: + when (node.kind) { + in NodeKind.classLike, + NodeKind.ExternalClass, + NodeKind.Exception -> module.classNamed(qualifiedTypeName()) ?: ClassDocumentationNodeAdapter(module, node) + + else -> when { + node.links.isNotEmpty() -> TypeAdapter(module, node.links.first()).asClassDoc() + else -> ClassDocumentationNodeAdapter(module, node) // TODO ? + } + } + + override fun asTypeVariable(): TypeVariable? = if (node.kind == NodeKind.TypeParameter) TypeVariableAdapter(module, node) else null + override fun asParameterizedType(): ParameterizedType? = + if (node.details(NodeKind.Type).isNotEmpty() && javaLanguageService.getArrayElementType(node) == null) + ParameterizedTypeAdapter(module, node) + else + null + + override fun asAnnotationTypeDoc(): AnnotationTypeDoc? = if (node.kind == NodeKind.AnnotationClass) AnnotationTypeDocAdapter(module, node) else null + override fun asAnnotatedType(): AnnotatedType? = if (node.annotations.isNotEmpty()) AnnotatedTypeAdapter(module, node) else null + override fun getElementType(): Type? = javaLanguageService.getArrayElementType(node)?.let { et -> TypeAdapter(module, et) } + override fun asWildcardType(): WildcardType? = null + + override fun toString(): String = qualifiedTypeName() + dimension() + override fun hashCode(): Int = node.name.hashCode() + override fun equals(other: Any?): Boolean = other is TypeAdapter && toString() == other.toString() +} + +class NotAnnotatedTypeAdapter(typeAdapter: AnnotatedTypeAdapter) : Type by typeAdapter { + override fun asAnnotatedType() = null +} + +class AnnotatedTypeAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : TypeAdapter(module, node), AnnotatedType { + override fun underlyingType(): Type? = NotAnnotatedTypeAdapter(this) + override fun annotations(): Array = nodeAnnotations(this).toTypedArray() +} + +class WildcardTypeAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : TypeAdapter(module, node), WildcardType { + override fun extendsBounds(): Array = node.details(NodeKind.UpperBound).map { TypeAdapter(module, it) }.toTypedArray() + override fun superBounds(): Array = node.details(NodeKind.LowerBound).map { TypeAdapter(module, it) }.toTypedArray() +} + +class TypeVariableAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : TypeAdapter(module, node), TypeVariable { + override fun owner(): ProgramElementDoc = node.owner!!.let { owner -> + when (owner.kind) { + NodeKind.Function, + NodeKind.Constructor -> ExecutableMemberAdapter(module, owner) + + NodeKind.Class, + NodeKind.Interface, + NodeKind.Enum -> ClassDocumentationNodeAdapter(module, owner) + + else -> ProgramElementAdapter(module, node.owner!!) + } + } + + override fun bounds(): Array? = node.details(NodeKind.UpperBound).map { TypeAdapter(module, it) }.toTypedArray() + override fun annotations(): Array? = node.members(NodeKind.Annotation).map { AnnotationDescAdapter(module, it) }.toTypedArray() + + override fun qualifiedTypeName(): String = node.name + override fun simpleTypeName(): String = node.name + override fun typeName(): String = node.name + + override fun hashCode(): Int = node.name.hashCode() + override fun equals(other: Any?): Boolean = other is Type && other.typeName() == typeName() && other.asTypeVariable()?.owner() == owner() + + override fun asTypeVariable(): TypeVariableAdapter = this +} + +class ParameterizedTypeAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : TypeAdapter(module, node), ParameterizedType { + override fun typeArguments(): Array = node.details(NodeKind.Type).map { TypeVariableAdapter(module, it) }.toTypedArray() + override fun superclassType(): Type? = + node.lookupSuperClasses(module) + .firstOrNull { it.kind == NodeKind.Class || it.kind == NodeKind.ExternalClass } + ?.let { ClassDocumentationNodeAdapter(module, it) } + + override fun interfaceTypes(): Array = + node.lookupSuperClasses(module) + .filter { it.kind == NodeKind.Interface } + .map { ClassDocumentationNodeAdapter(module, it) } + .toTypedArray() + + override fun containingType(): Type? = when (node.owner?.kind) { + NodeKind.Package -> null + NodeKind.Class, + NodeKind.Interface, + NodeKind.Object, + NodeKind.Enum -> ClassDocumentationNodeAdapter(module, node.owner!!) + + else -> null + } +} + +class ParameterAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), Parameter { + override fun typeName(): String? = type()?.typeName() + override fun type(): Type? = TypeAdapter(module, node.detail(NodeKind.Type)) + override fun annotations(): Array = nodeAnnotations(this).toTypedArray() +} + +class ReceiverParameterAdapter(module: ModuleNodeAdapter, val receiverType: DocumentationNode, val parent: ExecutableMemberAdapter) : DocumentationNodeAdapter(module, receiverType), Parameter { + override fun typeName(): String? = receiverType.name + override fun type(): Type? = TypeAdapter(module, receiverType) + override fun annotations(): Array = nodeAnnotations(this).toTypedArray() + override fun name(): String = tryName("receiver") + + private tailrec fun tryName(name: String): String = when (name) { + in parent.parameters().drop(1).map { it.name() } -> tryName("$$name") + else -> name + } +} + +fun classOf(fqName: String, kind: NodeKind = NodeKind.Class) = DocumentationNode(fqName.substringAfterLast(".", fqName), Content.Empty, kind).let { node -> + val pkg = fqName.substringBeforeLast(".", "") + if (pkg.isNotEmpty()) { + node.append(DocumentationNode(pkg, Content.Empty, NodeKind.Package), RefKind.Owner) + } + + node +} + +private fun DocumentationNode.hasNonEmptyContent() = + this.content.summary !is ContentEmpty || this.content.description !is ContentEmpty || this.content.sections.isNotEmpty() + + +open class ExecutableMemberAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : ProgramElementAdapter(module, node), ExecutableMemberDoc { + + override fun isSynthetic(): Boolean = false + override fun isNative(): Boolean = node.annotations.any { it.name == "native" } + + override fun thrownExceptions(): Array = emptyArray() // TODO + override fun throwsTags(): Array = + node.content.sections + .filter { it.tag == ContentTags.Exceptions && it.subjectName != null } + .map { ThrowsTagAdapter(this, ClassDocumentationNodeAdapter(module, classOf(it.subjectName!!, NodeKind.Exception)), it.children) } + .toTypedArray() + + override fun isVarArgs(): Boolean = node.details(NodeKind.Parameter).last().hasModifier("vararg") + + override fun isSynchronized(): Boolean = node.annotations.any { it.name == "synchronized" } + + override fun paramTags(): Array = + collectParamTags(NodeKind.Parameter, sectionFilter = { it.subjectName in parameters().map { it.name() } }) + + override fun thrownExceptionTypes(): Array = emptyArray() + override fun receiverType(): Type? = receiverNode()?.let { receiver -> TypeAdapter(module, receiver) } + override fun flatSignature(): String = node.details(NodeKind.Parameter).map { JavaLanguageService().renderType(it) }.joinToString(", ", "(", ")") + override fun signature(): String = node.details(NodeKind.Parameter).map { JavaLanguageService().renderType(it) }.joinToString(", ", "(", ")") // TODO it should be FQ types + + override fun parameters(): Array = + ((receiverNode()?.let { receiver -> listOf(ReceiverParameterAdapter(module, receiver, this)) } ?: emptyList()) + + node.details(NodeKind.Parameter).map { ParameterAdapter(module, it) } + ).toTypedArray() + + override fun typeParameters(): Array = node.details(NodeKind.TypeParameter).map { TypeVariableAdapter(module, it) }.toTypedArray() + + override fun typeParamTags(): Array = + collectParamTags(NodeKind.TypeParameter, sectionFilter = { it.subjectName in typeParameters().map { it.simpleTypeName() } }) + + private fun receiverNode() = node.details(NodeKind.Receiver).let { receivers -> + when { + receivers.isNotEmpty() -> receivers.single().detail(NodeKind.Type) + else -> null + } + } +} + +class ConstructorAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : ExecutableMemberAdapter(module, node), ConstructorDoc { + override fun name(): String = node.owner?.name ?: throw IllegalStateException("No owner for $node") + + override fun containingClass(): ClassDoc? { + return super.containingClass() + } +} + +class MethodAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : ExecutableMemberAdapter(module, node), MethodDoc { + override fun overrides(meth: MethodDoc?): Boolean = false // TODO + + override fun overriddenType(): Type? = node.overrides.firstOrNull()?.owner?.let { owner -> TypeAdapter(module, owner) } + + override fun overriddenMethod(): MethodDoc? = node.overrides.map { MethodAdapter(module, it) }.firstOrNull() + override fun overriddenClass(): ClassDoc? = overriddenMethod()?.containingClass() + + override fun isAbstract(): Boolean = false // TODO + + override fun isDefault(): Boolean = false + + override fun returnType(): Type = TypeAdapter(module, node.detail(NodeKind.Type)) + + override fun tags(tagname: String?) = super.tags(tagname) + + override fun tags(): Array { + val tags = super.tags().toMutableList() + node.content.findSectionByTag(ContentTags.Return)?.let { + tags += ReturnTagAdapter(module, this, it.children) + } + + return tags.toTypedArray() + } +} + +class FieldAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : ProgramElementAdapter(module, node), FieldDoc { + override fun isSynthetic(): Boolean = false + + override fun constantValueExpression(): String? = node.detailOrNull(NodeKind.Value)?.let { it.name } + override fun constantValue(): Any? = constantValueExpression() + + override fun type(): Type = TypeAdapter(module, node.detail(NodeKind.Type)) + override fun isTransient(): Boolean = node.hasAnnotation(Transient::class) + override fun serialFieldTags(): Array = emptyArray() + + override fun isVolatile(): Boolean = node.hasAnnotation(Volatile::class) +} +open class ClassDocumentationNodeAdapter(module: ModuleNodeAdapter, val classNode: DocumentationNode) + : ProgramElementAdapter(module, classNode), + Type by TypeAdapter(module, classNode), + ClassDoc, + AnnotationTypeDoc { + + override fun elements(): Array? = emptyArray() // TODO + + override fun name(): String { + val parent = classNode.owner + if (parent?.kind in NodeKind.classLike) { + return parent!!.name + "." + classNode.name + } + return classNode.simpleName() + } + + override fun qualifiedName(): String? { + return super.qualifiedName() + } + override fun constructors(filter: Boolean): Array = classNode.members(NodeKind.Constructor).map { ConstructorAdapter(module, it) }.toTypedArray() + override fun constructors(): Array = constructors(true) + override fun importedPackages(): Array = emptyArray() + override fun importedClasses(): Array? = emptyArray() + override fun typeParameters(): Array = classNode.details(NodeKind.TypeParameter).map { TypeVariableAdapter(module, it) }.toTypedArray() + override fun asTypeVariable(): TypeVariable? = if (classNode.kind == NodeKind.Class) TypeVariableAdapter(module, classNode) else null + override fun isExternalizable(): Boolean = interfaces().any { it.qualifiedName() == "java.io.Externalizable" } + override fun definesSerializableFields(): Boolean = false + override fun methods(filter: Boolean): Array = classNode.members(NodeKind.Function).map { MethodAdapter(module, it) }.toTypedArray() // TODO include get/set methods + override fun methods(): Array = methods(true) + override fun enumConstants(): Array? = classNode.members(NodeKind.EnumItem).map { FieldAdapter(module, it) }.toTypedArray() + override fun isAbstract(): Boolean = classNode.details(NodeKind.Modifier).any { it.name == "abstract" } + override fun interfaceTypes(): Array = classNode.lookupSuperClasses(module) + .filter { it.kind == NodeKind.Interface } + .map { ClassDocumentationNodeAdapter(module, it) } + .toTypedArray() + + override fun interfaces(): Array = classNode.lookupSuperClasses(module) + .filter { it.kind == NodeKind.Interface } + .map { ClassDocumentationNodeAdapter(module, it) } + .toTypedArray() + + override fun typeParamTags(): Array = + collectParamTags(NodeKind.TypeParameter, sectionFilter = { it.subjectName in typeParameters().map { it.simpleTypeName() } }) + + override fun fields(): Array = fields(true) + override fun fields(filter: Boolean): Array = classNode.members(NodeKind.Field).map { FieldAdapter(module, it) }.toTypedArray() + + override fun findClass(className: String?): ClassDoc? = null // TODO !!! + override fun serializableFields(): Array = emptyArray() + override fun superclassType(): Type? = classNode.lookupSuperClasses(module).singleOrNull { it.kind == NodeKind.Class }?.let { ClassDocumentationNodeAdapter(module, it) } + override fun serializationMethods(): Array = emptyArray() // TODO + override fun superclass(): ClassDoc? = classNode.lookupSuperClasses(module).singleOrNull { it.kind == NodeKind.Class }?.let { ClassDocumentationNodeAdapter(module, it) } + override fun isSerializable(): Boolean = false // TODO + override fun subclassOf(cd: ClassDoc?): Boolean { + if (cd == null) { + return false + } + + val expectedFQName = cd.qualifiedName() + val types = arrayListOf(classNode) + val visitedTypes = HashSet() + + while (types.isNotEmpty()) { + val type = types.removeAt(types.lastIndex) + val fqName = type.qualifiedName() + + if (expectedFQName == fqName) { + return true + } + + visitedTypes.add(fqName) + types.addAll(type.details(NodeKind.Supertype).filter { it.qualifiedName() !in visitedTypes }) + } + + return false + } + + override fun innerClasses(): Array = classNode.members(NodeKind.Class).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray() + override fun innerClasses(filter: Boolean): Array = innerClasses() +} + +fun DocumentationNode.lookupSuperClasses(module: ModuleNodeAdapter) = + details(NodeKind.Supertype) + .map { it.links.firstOrNull() }.mapNotNull { module.allTypes[it?.qualifiedName()] } + +fun List.collectAllTypesRecursively(): Map { + val result = hashMapOf() + + fun DocumentationNode.collectTypesRecursively() { + val classLikeMembers = NodeKind.classLike.flatMap { members(it) } + classLikeMembers.forEach { + result.put(it.qualifiedName(), it) + it.collectTypesRecursively() + } + } + + forEach { it.collectTypesRecursively() } + return result +} + +class ModuleNodeAdapter(val module: DocumentationModule, val reporter: DocErrorReporter, val outputPath: String) : DocumentationNodeBareAdapter(module), DocErrorReporter by reporter, RootDoc { + val allPackages = module.members(NodeKind.Package).associateBy { it.name } + val allTypes = module.members(NodeKind.Package).collectAllTypesRecursively() + + override fun packageNamed(name: String?): PackageDoc? = allPackages[name]?.let { PackageAdapter(this, it) } + + override fun classes(): Array = + allTypes.values.map { ClassDocumentationNodeAdapter(this, it) }.toTypedArray() + + override fun options(): Array> = arrayOf( + arrayOf("-d", outputPath), + arrayOf("-docencoding", "UTF-8"), + arrayOf("-charset", "UTF-8"), + arrayOf("-keywords") + ) + + override fun specifiedPackages(): Array? = module.members(NodeKind.Package).map { PackageAdapter(this, it) }.toTypedArray() + + override fun classNamed(qualifiedName: String?): ClassDoc? = + allTypes[qualifiedName]?.let { ClassDocumentationNodeAdapter(this, it) } + + override fun specifiedClasses(): Array = classes() +} + +private fun DocumentationNodeAdapter.collectParamTags(kind: NodeKind, sectionFilter: (ContentSection) -> Boolean) = + (node.details(kind) + .filter(DocumentationNode::hasNonEmptyContent) + .map { ParamTagAdapter(module, this, it.name, true, it.content.children) } + + + node.content.sections + .filter(sectionFilter) + .map { + ParamTagAdapter(module, this, it.subjectName ?: "?", true, + it.children.filterNot { contentNode -> contentNode is LazyContentBlock } + ) + } + ) + .distinctBy { it.parameterName } + .toTypedArray() \ No newline at end of file diff --git a/plugins/javadoc8/src/main/kotlin/javadoc/dokka-adapters.kt b/plugins/javadoc8/src/main/kotlin/javadoc/dokka-adapters.kt new file mode 100644 index 00000000..1329876a --- /dev/null +++ b/plugins/javadoc8/src/main/kotlin/javadoc/dokka-adapters.kt @@ -0,0 +1,42 @@ +package org.jetbrains.dokka.javadoc + +import com.google.inject.Binder +import com.google.inject.Inject +import com.sun.tools.doclets.formats.html.HtmlDoclet +import org.jetbrains.dokka.* +import org.jetbrains.dokka.Formats.DefaultAnalysisComponent +import org.jetbrains.dokka.Formats.DefaultAnalysisComponentServices +import org.jetbrains.dokka.Formats.FormatDescriptor +import org.jetbrains.dokka.Formats.KotlinAsJava +import org.jetbrains.dokka.Utilities.bind +import org.jetbrains.dokka.Utilities.toType + +class JavadocGenerator @Inject constructor(val configuration: DokkaConfiguration, val logger: DokkaLogger) : Generator { + + override fun buildPages(nodes: Iterable) { + val module = nodes.single() as DocumentationModule + + HtmlDoclet.start(ModuleNodeAdapter(module, StandardReporter(logger), configuration.outputDir)) + } + + override fun buildOutlines(nodes: Iterable) { + // no outline could be generated separately + } + + override fun buildSupportFiles() { + } + + override fun buildPackageList(nodes: Iterable) { + // handled by javadoc itself + } +} + +class JavadocFormatDescriptor : + FormatDescriptor, + DefaultAnalysisComponent, + DefaultAnalysisComponentServices by KotlinAsJava { + + override fun configureOutput(binder: Binder): Unit = with(binder) { + bind() toType JavadocGenerator::class + } +} diff --git a/plugins/javadoc8/src/main/kotlin/javadoc/reporter.kt b/plugins/javadoc8/src/main/kotlin/javadoc/reporter.kt new file mode 100644 index 00000000..fc38368c --- /dev/null +++ b/plugins/javadoc8/src/main/kotlin/javadoc/reporter.kt @@ -0,0 +1,34 @@ +package org.jetbrains.dokka.javadoc + +import com.sun.javadoc.DocErrorReporter +import com.sun.javadoc.SourcePosition +import org.jetbrains.dokka.DokkaLogger + +class StandardReporter(val logger: DokkaLogger) : DocErrorReporter { + override fun printWarning(msg: String?) { + logger.warn(msg.toString()) + } + + override fun printWarning(pos: SourcePosition?, msg: String?) { + logger.warn(format(pos, msg)) + } + + override fun printError(msg: String?) { + logger.error(msg.toString()) + } + + override fun printError(pos: SourcePosition?, msg: String?) { + logger.error(format(pos, msg)) + } + + override fun printNotice(msg: String?) { + logger.info(msg.toString()) + } + + override fun printNotice(pos: SourcePosition?, msg: String?) { + logger.info(format(pos, msg)) + } + + private fun format(pos: SourcePosition?, msg: String?) = + if (pos == null) msg.toString() else "${pos.file()}:${pos.line()}:${pos.column()}: $msg" +} \ No newline at end of file diff --git a/plugins/javadoc8/src/main/kotlin/javadoc/source-position.kt b/plugins/javadoc8/src/main/kotlin/javadoc/source-position.kt new file mode 100644 index 00000000..6125f968 --- /dev/null +++ b/plugins/javadoc8/src/main/kotlin/javadoc/source-position.kt @@ -0,0 +1,19 @@ +package org.jetbrains.dokka.javadoc + +import com.sun.javadoc.SourcePosition +import org.jetbrains.dokka.DocumentationNode +import org.jetbrains.dokka.NodeKind +import java.io.File + +class SourcePositionAdapter(val docNode: DocumentationNode) : SourcePosition { + + private val sourcePositionParts: List by lazy { + docNode.details(NodeKind.SourcePosition).firstOrNull()?.name?.split(":") ?: emptyList() + } + + override fun file(): File? = if (sourcePositionParts.isEmpty()) null else File(sourcePositionParts[0]) + + override fun line(): Int = sourcePositionParts.getOrNull(1)?.toInt() ?: -1 + + override fun column(): Int = sourcePositionParts.getOrNull(2)?.toInt() ?: -1 +} diff --git a/plugins/javadoc8/src/main/kotlin/javadoc/tags.kt b/plugins/javadoc8/src/main/kotlin/javadoc/tags.kt new file mode 100644 index 00000000..99c9bfff --- /dev/null +++ b/plugins/javadoc8/src/main/kotlin/javadoc/tags.kt @@ -0,0 +1,228 @@ +package org.jetbrains.dokka.javadoc + +import com.sun.javadoc.* +import org.jetbrains.dokka.* +import java.util.* + +class TagImpl(val holder: Doc, val name: String, val text: String): Tag { + override fun text(): String? = text + + override fun holder(): Doc = holder + override fun firstSentenceTags(): Array? = arrayOf() + override fun inlineTags(): Array? = arrayOf() + + override fun name(): String = name + override fun kind(): String = name + + override fun position(): SourcePosition = holder.position() +} + +class TextTag(val holder: Doc, val content: ContentText) : Tag { + val plainText: String + get() = content.text + + override fun name(): String = "Text" + override fun kind(): String = name() + override fun text(): String? = plainText + override fun inlineTags(): Array = arrayOf(this) + override fun holder(): Doc = holder + override fun firstSentenceTags(): Array = arrayOf(this) + override fun position(): SourcePosition = holder.position() +} + +abstract class SeeTagAdapter(val holder: Doc, val content: ContentNodeLink) : SeeTag { + override fun position(): SourcePosition? = holder.position() + override fun name(): String = "@see" + override fun kind(): String = "@see" + override fun holder(): Doc = holder + + override fun text(): String? = content.node?.name ?: "(?)" +} + +class SeeExternalLinkTagAdapter(val holder: Doc, val link: ContentExternalLink) : SeeTag { + override fun position(): SourcePosition = holder.position() + override fun text(): String = label() + override fun inlineTags(): Array = emptyArray() // TODO + + override fun label(): String { + val label = link.asText() ?: link.href + return "$label" + } + + override fun referencedPackage(): PackageDoc? = null + override fun referencedClass(): ClassDoc? = null + override fun referencedMemberName(): String? = null + override fun referencedClassName(): String? = null + override fun referencedMember(): MemberDoc? = null + override fun holder(): Doc = holder + override fun firstSentenceTags(): Array = inlineTags() + override fun name(): String = "@link" + override fun kind(): String = "@see" +} + +fun ContentBlock.asText(): String? { + val contentText = children.singleOrNull() as? ContentText + return contentText?.text +} + +class SeeMethodTagAdapter(holder: Doc, val method: MethodAdapter, content: ContentNodeLink) : SeeTagAdapter(holder, content) { + override fun referencedMember(): MemberDoc = method + override fun referencedMemberName(): String = method.name() + override fun referencedPackage(): PackageDoc? = null + override fun referencedClass(): ClassDoc? = method.containingClass() + override fun referencedClassName(): String = method.containingClass()?.name() ?: "" + override fun label(): String = content.text ?: "${method.containingClass()?.name()}.${method.name()}" + + override fun inlineTags(): Array = emptyArray() // TODO + override fun firstSentenceTags(): Array = inlineTags() // TODO +} + +class SeeClassTagAdapter(holder: Doc, val clazz: ClassDocumentationNodeAdapter, content: ContentNodeLink) : SeeTagAdapter(holder, content) { + override fun referencedMember(): MemberDoc? = null + override fun referencedMemberName(): String? = null + override fun referencedPackage(): PackageDoc? = null + override fun referencedClass(): ClassDoc = clazz + override fun referencedClassName(): String = clazz.name() + override fun label(): String = "${clazz.classNode.kind.name.toLowerCase()} ${clazz.name()}" + + override fun inlineTags(): Array = emptyArray() // TODO + override fun firstSentenceTags(): Array = inlineTags() // TODO +} + +class ParamTagAdapter(val module: ModuleNodeAdapter, + val holder: Doc, + val parameterName: String, + val typeParameter: Boolean, + val content: List) : ParamTag { + + constructor(module: ModuleNodeAdapter, holder: Doc, parameterName: String, isTypeParameter: Boolean, content: ContentNode) + : this(module, holder, parameterName, isTypeParameter, listOf(content)) { + } + + override fun name(): String = "@param" + override fun kind(): String = name() + override fun holder(): Doc = holder + override fun position(): SourcePosition? = holder.position() + + override fun text(): String = "@param $parameterName ${parameterComment()}" // Seems has no effect, so used for debug + override fun inlineTags(): Array = buildInlineTags(module, holder, content).toTypedArray() + override fun firstSentenceTags(): Array = arrayOf(TextTag(holder, ContentText(text()))) + + override fun isTypeParameter(): Boolean = typeParameter + override fun parameterComment(): String = content.toString() // TODO + override fun parameterName(): String = parameterName +} + + +class ThrowsTagAdapter(val holder: Doc, val type: ClassDocumentationNodeAdapter, val content: List) : ThrowsTag { + override fun name(): String = "@throws" + override fun kind(): String = name() + override fun holder(): Doc = holder + override fun position(): SourcePosition? = holder.position() + + override fun text(): String = "${name()} ${exceptionName()} ${exceptionComment()}" + override fun inlineTags(): Array = buildInlineTags(type.module, holder, content).toTypedArray() + override fun firstSentenceTags(): Array = emptyArray() + + override fun exceptionComment(): String = content.toString() + override fun exceptionType(): Type = type + override fun exception(): ClassDoc = type + override fun exceptionName(): String = type.qualifiedTypeName() +} + +class ReturnTagAdapter(val module: ModuleNodeAdapter, val holder: Doc, val content: List) : Tag { + override fun name(): String = "@return" + override fun kind() = name() + override fun holder() = holder + override fun position(): SourcePosition? = holder.position() + + override fun text(): String = "@return $content" // Seems has no effect, so used for debug + override fun inlineTags(): Array = buildInlineTags(module, holder, content).toTypedArray() + override fun firstSentenceTags(): Array = inlineTags() +} + +fun buildInlineTags(module: ModuleNodeAdapter, holder: Doc, tags: List): List = ArrayList().apply { tags.forEach { buildInlineTags(module, holder, it, this) } } + +fun buildInlineTags(module: ModuleNodeAdapter, holder: Doc, root: ContentNode): List = ArrayList().apply { buildInlineTags(module, holder, root, this) } + +private fun buildInlineTags(module: ModuleNodeAdapter, holder: Doc, nodes: List, result: MutableList) { + nodes.forEach { + buildInlineTags(module, holder, it, result) + } +} + + +private fun buildInlineTags(module: ModuleNodeAdapter, holder: Doc, node: ContentNode, result: MutableList) { + fun surroundWith(module: ModuleNodeAdapter, holder: Doc, prefix: String, postfix: String, node: ContentBlock, result: MutableList) { + if (node.children.isNotEmpty()) { + val open = TextTag(holder, ContentText(prefix)) + val close = TextTag(holder, ContentText(postfix)) + + result.add(open) + buildInlineTags(module, holder, node.children, result) + + if (result.last() === open) { + result.removeAt(result.lastIndex) + } else { + result.add(close) + } + } + } + + fun surroundWith(module: ModuleNodeAdapter, holder: Doc, prefix: String, postfix: String, node: ContentNode, result: MutableList) { + if (node !is ContentEmpty) { + val open = TextTag(holder, ContentText(prefix)) + val close = TextTag(holder, ContentText(postfix)) + + result.add(open) + buildInlineTags(module, holder, node, result) + if (result.last() === open) { + result.removeAt(result.lastIndex) + } else { + result.add(close) + } + } + } + + when (node) { + is ContentText -> result.add(TextTag(holder, node)) + is ContentNodeLink -> { + val target = node.node + when (target?.kind) { + NodeKind.Function -> result.add(SeeMethodTagAdapter(holder, MethodAdapter(module, node.node!!), node)) + + in NodeKind.classLike -> result.add(SeeClassTagAdapter(holder, ClassDocumentationNodeAdapter(module, node.node!!), node)) + + else -> buildInlineTags(module, holder, node.children, result) + } + } + is ContentExternalLink -> result.add(SeeExternalLinkTagAdapter(holder, node)) + is ContentCode -> surroundWith(module, holder, "", "", node, result) + is ContentBlockCode -> surroundWith(module, holder, "
", "
", node, result) + is ContentEmpty -> {} + is ContentEmphasis -> surroundWith(module, holder, "", "", node, result) + is ContentHeading -> surroundWith(module, holder, "", "", node, result) + is ContentEntity -> result.add(TextTag(holder, ContentText(node.text))) // TODO ?? + is ContentIdentifier -> result.add(TextTag(holder, ContentText(node.text))) // TODO + is ContentKeyword -> result.add(TextTag(holder, ContentText(node.text))) // TODO + is ContentListItem -> surroundWith(module, holder, "
  • ", "
  • ", node, result) + is ContentOrderedList -> surroundWith(module, holder, "
      ", "
    ", node, result) + is ContentUnorderedList -> surroundWith(module, holder, "
      ", "
    ", node, result) + is ContentParagraph -> surroundWith(module, holder, "

    ", "

    ", node, result) + is ContentSection -> surroundWith(module, holder, "

    ", "

    ", node, result) // TODO how section should be represented? + is ContentNonBreakingSpace -> result.add(TextTag(holder, ContentText(" "))) + is ContentStrikethrough -> surroundWith(module, holder, "", "", node, result) + is ContentStrong -> surroundWith(module, holder, "", "", node, result) + is ContentSymbol -> result.add(TextTag(holder, ContentText(node.text))) // TODO? + is Content -> { + surroundWith(module, holder, "

    ", "

    ", node.summary, result) + surroundWith(module, holder, "

    ", "

    ", node.description, result) + } + is ContentBlock -> { + surroundWith(module, holder, "", "", node, result) + } + is ContentHardLineBreak -> result.add(TextTag(holder, ContentText("
    "))) + + else -> result.add(TextTag(holder, ContentText("$node"))) + } +} \ No newline at end of file diff --git a/plugins/javadoc8/src/main/resources/dokka/format/javadoc.properties b/plugins/javadoc8/src/main/resources/dokka/format/javadoc.properties new file mode 100644 index 00000000..a0d8a945 --- /dev/null +++ b/plugins/javadoc8/src/main/resources/dokka/format/javadoc.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.javadoc.JavadocFormatDescriptor +description=Produces Javadoc, with Kotlin declarations as Java view \ No newline at end of file diff --git a/plugins/javadoc8/src/test/kotlin/javadoc/JavadocTest.kt b/plugins/javadoc8/src/test/kotlin/javadoc/JavadocTest.kt new file mode 100644 index 00000000..1c4dd258 --- /dev/null +++ b/plugins/javadoc8/src/test/kotlin/javadoc/JavadocTest.kt @@ -0,0 +1,333 @@ +package org.jetbrains.dokka.javadoc + +import com.sun.javadoc.Tag +import com.sun.javadoc.Type +import org.jetbrains.dokka.DokkaConsoleLogger +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.tests.ModelConfig +import org.jetbrains.dokka.tests.assertEqualsIgnoringSeparators +import org.jetbrains.dokka.tests.checkSourceExistsAndVerifyModel +import org.junit.Assert.* +import org.junit.Test +import java.lang.reflect.Modifier.* + +class JavadocTest { + val defaultModelConfig = ModelConfig(analysisPlatform = Platform.jvm) + + @Test fun testTypes() { + verifyJavadoc("testdata/javadoc/types.kt", ModelConfig(analysisPlatform = Platform.jvm, withJdk = true)) { doc -> + val classDoc = doc.classNamed("foo.TypesKt")!! + val method = classDoc.methods().find { it.name() == "foo" }!! + + val type = method.returnType() + assertFalse(type.asClassDoc().isIncluded) + assertEquals("java.lang.String", type.qualifiedTypeName()) + assertEquals("java.lang.String", type.asClassDoc().qualifiedName()) + + val params = method.parameters() + assertTrue(params[0].type().isPrimitive) + assertFalse(params[1].type().asClassDoc().isIncluded) + } + } + + @Test fun testObject() { + verifyJavadoc("testdata/javadoc/obj.kt", defaultModelConfig) { doc -> + val classDoc = doc.classNamed("foo.O") + assertNotNull(classDoc) + + val companionDoc = doc.classNamed("foo.O.Companion") + assertNotNull(companionDoc) + + val pkgDoc = doc.packageNamed("foo")!! + assertEquals(2, pkgDoc.allClasses().size) + } + } + + @Test fun testException() { + verifyJavadoc( + "testdata/javadoc/exception.kt", + ModelConfig(analysisPlatform = Platform.jvm, withKotlinRuntime = true) + ) { doc -> + val classDoc = doc.classNamed("foo.MyException")!! + val member = classDoc.methods().find { it.name() == "foo" } + assertEquals(classDoc, member!!.containingClass()) + } + } + + @Test fun testByteArray() { + verifyJavadoc( + "testdata/javadoc/bytearr.kt", + ModelConfig(analysisPlatform = Platform.jvm, withKotlinRuntime = true) + ) { doc -> + val classDoc = doc.classNamed("foo.ByteArray")!! + assertNotNull(classDoc.asClassDoc()) + + val member = classDoc.methods().find { it.name() == "foo" }!! + assertEquals("[]", member.returnType().dimension()) + } + } + + @Test fun testStringArray() { + verifyJavadoc( + "testdata/javadoc/stringarr.kt", + ModelConfig(analysisPlatform = Platform.jvm, withKotlinRuntime = true) + ) { doc -> + val classDoc = doc.classNamed("foo.Foo")!! + assertNotNull(classDoc.asClassDoc()) + + val member = classDoc.methods().find { it.name() == "main" }!! + val paramType = member.parameters()[0].type() + assertNull(paramType.asParameterizedType()) + assertEquals("String[]", paramType.typeName()) + assertEquals("String", paramType.asClassDoc().name()) + } + } + + @Test fun testJvmName() { + verifyJavadoc( + "testdata/javadoc/jvmname.kt", + ModelConfig(analysisPlatform = Platform.jvm, withKotlinRuntime = true) + ) { doc -> + val classDoc = doc.classNamed("foo.Apple")!! + assertNotNull(classDoc.asClassDoc()) + + val member = classDoc.methods().find { it.name() == "_tree" } + assertNotNull(member) + } + } + + @Test fun testLinkWithParam() { + verifyJavadoc( + "testdata/javadoc/paramlink.kt", + ModelConfig(analysisPlatform = Platform.jvm, withKotlinRuntime = true) + ) { doc -> + val classDoc = doc.classNamed("demo.Apple")!! + assertNotNull(classDoc.asClassDoc()) + val tags = classDoc.inlineTags().filterIsInstance() + assertEquals(2, tags.size) + val linkTag = tags[1] as SeeMethodTagAdapter + assertEquals("cutIntoPieces", linkTag.method.name()) + } + } + + @Test fun testInternalVisibility() { + verifyJavadoc( + "testdata/javadoc/internal.kt", + ModelConfig(analysisPlatform = Platform.jvm, withKotlinRuntime = true, includeNonPublic = false) + ) { doc -> + val classDoc = doc.classNamed("foo.Person")!! + val constructors = classDoc.constructors() + assertEquals(1, constructors.size) + assertEquals(1, constructors.single().parameters().size) + } + } + + @Test fun testSuppress() { + verifyJavadoc( + "testdata/javadoc/suppress.kt", + ModelConfig(analysisPlatform = Platform.jvm, withKotlinRuntime = true) + ) { doc -> + assertNull(doc.classNamed("Some")) + assertNull(doc.classNamed("SomeAgain")) + assertNull(doc.classNamed("Interface")) + val classSame = doc.classNamed("Same")!! + assertTrue(classSame.fields().isEmpty()) + assertTrue(classSame.methods().isEmpty()) + } + } + + @Test fun testTypeAliases() { + verifyJavadoc( + "testdata/javadoc/typealiases.kt", + ModelConfig(analysisPlatform = Platform.jvm, withKotlinRuntime = true) + ) { doc -> + assertNull(doc.classNamed("B")) + assertNull(doc.classNamed("D")) + + assertEquals("A", doc.classNamed("C")!!.superclass().name()) + val methodParamType = doc.classNamed("TypealiasesKt")!!.methods() + .find { it.name() == "some" }!!.parameters().first() + .type() + assertEquals("kotlin.jvm.functions.Function1", methodParamType.qualifiedTypeName()) + assertEquals("? super A, C", + methodParamType.asParameterizedType().typeArguments().joinToString(transform = Type::qualifiedTypeName) + ) + } + } + + @Test fun testKDocKeywordsOnMethod() { + verifyJavadoc( + "testdata/javadoc/kdocKeywordsOnMethod.kt", + ModelConfig(analysisPlatform = Platform.jvm, withKotlinRuntime = true) + ) { doc -> + val method = doc.classNamed("KdocKeywordsOnMethodKt")!!.methods()[0] + assertEquals("@return [ContentText(text=value of a)]", method.tags("return").first().text()) + assertEquals("@param a [ContentText(text=Some string)]", method.paramTags().first().text()) + assertEquals("@throws FireException [ContentText(text=in case of fire)]", method.throwsTags().first().text()) + } + } + + @Test + fun testBlankLineInsideCodeBlock() { + verifyJavadoc( + "testdata/javadoc/blankLineInsideCodeBlock.kt", + ModelConfig(analysisPlatform = Platform.jvm, withKotlinRuntime = true) + ) { doc -> + val method = doc.classNamed("BlankLineInsideCodeBlockKt")!!.methods()[0] + val text = method.inlineTags().joinToString(separator = "", transform = Tag::text) + assertEqualsIgnoringSeparators(""" +

    +                This is a test
    +                    of Dokka's code blocks.
    +                Here is a blank line.
    +
    +                The previous line was blank.
    +                

    + """.trimIndent(), text) + } + } + + @Test + fun testCompanionMethodReference() { + verifyJavadoc("testdata/javadoc/companionMethodReference.kt", defaultModelConfig) { doc -> + val classDoc = doc.classNamed("foo.TestClass")!! + val tag = classDoc.inlineTags().filterIsInstance().first() + assertEquals("TestClass.Companion", tag.referencedClassName()) + assertEquals("test", tag.referencedMemberName()) + } + } + + @Test + fun testVararg() { + verifyJavadoc("testdata/javadoc/vararg.kt") { doc -> + val classDoc = doc.classNamed("VarargKt")!! + val methods = classDoc.methods() + methods.single { it.name() == "vararg" }.let { method -> + assertTrue(method.isVarArgs) + assertEquals("int", method.parameters().last().typeName()) + } + methods.single { it.name() == "varargInMiddle" }.let { method -> + assertFalse(method.isVarArgs) + assertEquals("int[]", method.parameters()[1].typeName()) + } + } + } + + @Test + fun shouldHaveValidVisibilityModifiers() { + verifyJavadoc("testdata/javadoc/visibilityModifiers.kt", ModelConfig(analysisPlatform = Platform.jvm, withKotlinRuntime = true)) { doc -> + val classDoc = doc.classNamed("foo.Apple")!! + val methods = classDoc.methods() + + val getName = methods[0] + val setName = methods[1] + val getWeight = methods[2] + val setWeight = methods[3] + val getRating = methods[4] + val setRating = methods[5] + val getCode = methods[6] + val color = classDoc.fields()[3] + val code = classDoc.fields()[4] + + assertTrue(getName.isProtected) + assertEquals(PROTECTED, getName.modifierSpecifier()) + assertTrue(setName.isProtected) + assertEquals(PROTECTED, setName.modifierSpecifier()) + + assertTrue(getWeight.isPublic) + assertEquals(PUBLIC, getWeight.modifierSpecifier()) + assertTrue(setWeight.isPublic) + assertEquals(PUBLIC, setWeight.modifierSpecifier()) + + assertTrue(getRating.isPublic) + assertEquals(PUBLIC, getRating.modifierSpecifier()) + assertTrue(setRating.isPublic) + assertEquals(PUBLIC, setRating.modifierSpecifier()) + + assertTrue(getCode.isPublic) + assertEquals(PUBLIC or STATIC, getCode.modifierSpecifier()) + + assertEquals(methods.size, 7) + + assertTrue(color.isPrivate) + assertEquals(PRIVATE, color.modifierSpecifier()) + + assertTrue(code.isPrivate) + assertTrue(code.isStatic) + assertEquals(PRIVATE or STATIC, code.modifierSpecifier()) + } + } + + @Test + fun shouldNotHaveDuplicatedConstructorParameters() { + verifyJavadoc("testdata/javadoc/constructorParameters.kt") { doc -> + val classDoc = doc.classNamed("bar.Banana")!! + val paramTags = classDoc.constructors()[0].paramTags() + + assertEquals(3, paramTags.size) + } + } + + @Test fun shouldHaveAllFunctionMarkedAsDeprecated() { + verifyJavadoc("testdata/javadoc/deprecated.java") { doc -> + val classDoc = doc.classNamed("bar.Banana")!! + + classDoc.methods().forEach { method -> + assertTrue(method.tags().any { it.kind() == "deprecated" }) + } + } + } + + @Test + fun testDefaultNoArgConstructor() { + verifyJavadoc("testdata/javadoc/defaultNoArgConstructor.kt") { doc -> + val classDoc = doc.classNamed("foo.Peach")!! + assertTrue(classDoc.constructors()[0].tags()[2].text() == "print peach") + } + } + + @Test + fun testNoArgConstructor() { + verifyJavadoc("testdata/javadoc/noArgConstructor.kt") { doc -> + val classDoc = doc.classNamed("foo.Plum")!! + assertTrue(classDoc.constructors()[0].tags()[2].text() == "print plum") + } + } + + @Test + fun testArgumentReference() { + verifyJavadoc("testdata/javadoc/argumentReference.kt") { doc -> + val classDoc = doc.classNamed("ArgumentReferenceKt")!! + val method = classDoc.methods().first() + val tag = method.seeTags().first() + assertEquals("argNamedError", tag.referencedMemberName()) + assertEquals("error", tag.label()) + } + } + + @Test + fun functionParameters() { + verifyJavadoc("testdata/javadoc/functionParameters.java") { doc -> + val tags = doc.classNamed("bar.Foo")!!.methods().first().paramTags() + assertEquals((tags.first() as ParamTagAdapter).content.size, 1) + assertEquals((tags[1] as ParamTagAdapter).content.size, 1) + } + } + + private fun verifyJavadoc(name: String, + modelConfig: ModelConfig = ModelConfig(), + callback: (ModuleNodeAdapter) -> Unit) { + + checkSourceExistsAndVerifyModel(name, + ModelConfig( + analysisPlatform = Platform.jvm, + format = "javadoc", + withJdk = modelConfig.withJdk, + withKotlinRuntime = modelConfig.withKotlinRuntime, + includeNonPublic = modelConfig.includeNonPublic + )) { model -> + val doc = ModuleNodeAdapter(model, StandardReporter(DokkaConsoleLogger), "") + callback(doc) + } + } +} -- cgit