From f724ba6fc6cddfe09015b5ee1d66122c158f11ba Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Tue, 3 Nov 2015 19:36:48 +0100 Subject: build Kotlin documentation for Java files based on descriptors --- src/Analysis/AnalysisEnvironment.kt | 20 +- src/Analysis/CoreProjectFileIndex.kt | 489 ++++++++++++++++++++++++- src/Java/JavaDocumentationBuilder.kt | 422 --------------------- src/Java/JavaPsiDocumentationBuilder.kt | 426 +++++++++++++++++++++ src/Kotlin/DocumentationBuilder.kt | 74 +++- src/Kotlin/KotlinAsJavaDocumentationBuilder.kt | 2 +- src/main.kt | 14 +- test/data/format/javaCodeInParam.java | 2 - test/data/format/javaCodeInParam.md | 10 +- test/data/format/javaCodeLiteralTags.java | 2 - test/data/format/javaCodeLiteralTags.md | 10 +- test/data/format/javaDeprecated.html | 6 +- test/data/format/javaDeprecated.java | 2 - test/data/format/javaLinkTag.html | 21 +- test/data/format/javaLinkTag.java | 2 - test/data/format/javaLinkTagWithLabel.html | 21 +- test/data/format/javaLinkTagWithLabel.java | 2 - test/data/format/javaSeeTag.html | 19 +- test/data/format/javaSeeTag.java | 4 +- test/data/format/javaSpaceInAuthor.java | 2 - test/data/format/javaSpaceInAuthor.md | 10 +- test/data/format/javaSupertype.html | 21 +- test/data/format/javaSupertype.java | 2 - test/data/format/javadocHtml.java | 2 - test/data/format/javadocHtml.md | 8 +- test/data/format/javadocOrderedList.java | 2 - test/data/format/javadocOrderedList.md | 8 +- test/data/java/annotatedAnnotation.java | 3 +- test/data/java/arrayType.java | 2 - test/data/java/constructors.java | 2 - test/data/java/deprecation.java | 7 +- test/data/java/enumValues.java | 2 - test/data/java/field.java | 2 - test/data/java/inheritorLinks.java | 2 - test/data/java/innerClass.java | 2 - test/data/java/javaLangObject.java | 2 - test/data/java/member.java | 2 - test/data/java/memberWithModifiers.java | 4 +- test/data/java/staticMethod.java | 2 - test/data/java/superClass.java | 2 - test/data/java/typeParameter.java | 2 - test/data/java/varargs.java | 2 - test/src/TestAPI.kt | 56 ++- test/src/format/HtmlFormatTest.kt | 10 +- test/src/format/MarkdownFormatTest.kt | 10 +- test/src/model/JavaTest.kt | 69 ++-- 46 files changed, 1188 insertions(+), 598 deletions(-) delete mode 100644 src/Java/JavaDocumentationBuilder.kt create mode 100644 src/Java/JavaPsiDocumentationBuilder.kt diff --git a/src/Analysis/AnalysisEnvironment.kt b/src/Analysis/AnalysisEnvironment.kt index 561ac2f0..af9859d6 100644 --- a/src/Analysis/AnalysisEnvironment.kt +++ b/src/Analysis/AnalysisEnvironment.kt @@ -4,9 +4,13 @@ import com.intellij.core.CoreApplicationEnvironment import com.intellij.core.CoreModuleManager import com.intellij.mock.MockComponentManager import com.intellij.openapi.Disposable +import com.intellij.openapi.extensions.Extensions +import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.OrderEnumerationHandler import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.util.Disposer import com.intellij.psi.PsiElement import com.intellij.psi.search.GlobalSearchScope @@ -70,12 +74,24 @@ public class AnalysisEnvironment(val messageCollector: MessageCollector, body: A val environment = KotlinCoreEnvironment.createForProduction(this, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES) val projectComponentManager = environment.project as MockComponentManager - val moduleManager = CoreModuleManager(environment.project, this) + val projectFileIndex = CoreProjectFileIndex(environment.project, + environment.configuration.getList(CommonConfigurationKeys.CONTENT_ROOTS)) + + val moduleManager = object : CoreModuleManager(environment.project, this) { + override fun getModules(): Array = arrayOf(projectFileIndex.module) + } + CoreApplicationEnvironment.registerComponentInstance(projectComponentManager.picoContainer, ModuleManager::class.java, moduleManager) + Extensions.registerAreaClass("IDEA_MODULE", null) + CoreApplicationEnvironment.registerExtensionPoint(Extensions.getRootArea(), + OrderEnumerationHandler.EP_NAME, OrderEnumerationHandler.Factory::class.java) + projectComponentManager.registerService(ProjectFileIndex::class.java, - CoreProjectFileIndex()) + projectFileIndex) + projectComponentManager.registerService(ProjectRootManager::class.java, + CoreProjectRootManager(projectFileIndex)) projectComponentManager.registerService(LibraryModificationTracker::class.java, LibraryModificationTracker(environment.project)) projectComponentManager.registerService(KotlinCacheService::class.java, diff --git a/src/Analysis/CoreProjectFileIndex.kt b/src/Analysis/CoreProjectFileIndex.kt index 916eab73..a1362fde 100644 --- a/src/Analysis/CoreProjectFileIndex.kt +++ b/src/Analysis/CoreProjectFileIndex.kt @@ -1,17 +1,409 @@ package org.jetbrains.dokka +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.BaseComponent +import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.module.Module -import com.intellij.openapi.roots.ContentIterator -import com.intellij.openapi.roots.OrderEntry -import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.projectRoots.SdkAdditionalData +import com.intellij.openapi.projectRoots.SdkModificator +import com.intellij.openapi.projectRoots.SdkTypeId +import com.intellij.openapi.roots.* +import com.intellij.openapi.roots.impl.ProjectOrderEnumerator +import com.intellij.openapi.util.Condition +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.UserDataHolderBase +import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.util.messages.MessageBus import org.jetbrains.jps.model.module.JpsModuleSourceRootType +import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot +import org.jetbrains.kotlin.cli.jvm.config.JvmContentRoot +import org.jetbrains.kotlin.config.ContentRoot +import org.jetbrains.kotlin.config.KotlinSourceRoot +import org.picocontainer.PicoContainer +import java.io.File /** * Workaround for the lack of ability to create a ProjectFileIndex implementation using only * classes from projectModel-{api,impl}. */ -class CoreProjectFileIndex(): ProjectFileIndex { +class CoreProjectFileIndex(val project: Project, contentRoots: List) : ProjectFileIndex, ModuleFileIndex { + val sourceRoots = contentRoots.filter { it !is JvmClasspathRoot } + val classpathRoots = contentRoots.filterIsInstance() + + val module: Module = object : UserDataHolderBase(), Module { + override fun isDisposed(): Boolean { + throw UnsupportedOperationException() + } + + override fun getOptionValue(p0: String): String? { + throw UnsupportedOperationException() + } + + override fun clearOption(p0: String) { + throw UnsupportedOperationException() + } + + override fun getName(): String = "" + + override fun getModuleWithLibrariesScope(): GlobalSearchScope { + throw UnsupportedOperationException() + } + + override fun getModuleWithDependentsScope(): GlobalSearchScope { + throw UnsupportedOperationException() + } + + override fun getModuleContentScope(): GlobalSearchScope { + throw UnsupportedOperationException() + } + + override fun isLoaded(): Boolean { + throw UnsupportedOperationException() + } + + override fun setOption(p0: String, p1: String) { + throw UnsupportedOperationException() + } + + override fun getModuleWithDependenciesScope(): GlobalSearchScope { + throw UnsupportedOperationException() + } + + override fun getModuleWithDependenciesAndLibrariesScope(p0: Boolean): GlobalSearchScope { + throw UnsupportedOperationException() + } + + override fun getProject(): Project = project + + override fun getModuleContentWithDependenciesScope(): GlobalSearchScope { + throw UnsupportedOperationException() + } + + override fun getModuleFilePath(): String { + throw UnsupportedOperationException() + } + + override fun getModuleTestsWithDependentsScope(): GlobalSearchScope { + throw UnsupportedOperationException() + } + + override fun getModuleScope(): GlobalSearchScope { + throw UnsupportedOperationException() + } + + override fun getModuleScope(p0: Boolean): GlobalSearchScope { + throw UnsupportedOperationException() + } + + override fun getModuleRuntimeScope(p0: Boolean): GlobalSearchScope { + throw UnsupportedOperationException() + } + + override fun getModuleFile(): VirtualFile? { + throw UnsupportedOperationException() + } + + override fun getExtensions(p0: ExtensionPointName): Array { + throw UnsupportedOperationException() + } + + override fun getComponent(p0: String): BaseComponent? { + throw UnsupportedOperationException() + } + + override fun getComponent(p0: Class, p1: T): T { + throw UnsupportedOperationException() + } + + override fun getComponent(interfaceClass: Class): T? { + if (interfaceClass == ModuleRootManager::class.java) { + return moduleRootManager as T + } + throw UnsupportedOperationException() + } + + override fun getDisposed(): Condition<*> { + throw UnsupportedOperationException() + } + + override fun getComponents(p0: Class): Array { + throw UnsupportedOperationException() + } + + override fun getPicoContainer(): PicoContainer { + throw UnsupportedOperationException() + } + + override fun hasComponent(p0: Class<*>): Boolean { + throw UnsupportedOperationException() + } + + override fun getMessageBus(): MessageBus { + throw UnsupportedOperationException() + } + + override fun dispose() { + throw UnsupportedOperationException() + } + } + + private val sdk: Sdk = object : Sdk, RootProvider { + override fun getFiles(rootType: OrderRootType): Array = classpathRoots + .map { StandardFileSystems.local().findFileByPath(it.file.path) } + .filterNotNull() + .toTypedArray() + + override fun addRootSetChangedListener(p0: RootProvider.RootSetChangedListener) { + throw UnsupportedOperationException() + } + + override fun addRootSetChangedListener(p0: RootProvider.RootSetChangedListener, p1: Disposable) { + throw UnsupportedOperationException() + } + + override fun getUrls(p0: OrderRootType): Array { + throw UnsupportedOperationException() + } + + override fun removeRootSetChangedListener(p0: RootProvider.RootSetChangedListener) { + throw UnsupportedOperationException() + } + + override fun getSdkModificator(): SdkModificator { + throw UnsupportedOperationException() + } + + override fun getName(): String = "" + + override fun getRootProvider(): RootProvider = this + + override fun getHomePath(): String? { + throw UnsupportedOperationException() + } + + override fun getVersionString(): String? { + throw UnsupportedOperationException() + } + + override fun getSdkAdditionalData(): SdkAdditionalData? { + throw UnsupportedOperationException() + } + + override fun clone(): Any { + throw UnsupportedOperationException() + } + + override fun getSdkType(): SdkTypeId { + throw UnsupportedOperationException() + } + + override fun getHomeDirectory(): VirtualFile? { + throw UnsupportedOperationException() + } + + override fun getUserData(p0: Key): T? { + throw UnsupportedOperationException() + } + + override fun putUserData(p0: Key, p1: T?) { + throw UnsupportedOperationException() + } + } + + private val moduleSourceOrderEntry = object : ModuleSourceOrderEntry { + override fun getFiles(p0: OrderRootType?): Array { + throw UnsupportedOperationException() + } + + override fun getPresentableName(): String { + throw UnsupportedOperationException() + } + + override fun getUrls(p0: OrderRootType?): Array { + throw UnsupportedOperationException() + } + + override fun getOwnerModule(): Module = module + + override fun accept(p0: RootPolicy?, p1: R?): R { + throw UnsupportedOperationException() + } + + override fun isValid(): Boolean { + throw UnsupportedOperationException() + } + + override fun compareTo(other: OrderEntry?): Int { + throw UnsupportedOperationException() + } + + override fun getRootModel(): ModuleRootModel = moduleRootManager + + override fun isSynthetic(): Boolean { + throw UnsupportedOperationException() + } + } + + private val sdkOrderEntry = object : JdkOrderEntry { + override fun getJdkName(): String? { + throw UnsupportedOperationException() + } + + override fun getJdk(): Sdk = sdk + + override fun getFiles(p0: OrderRootType?): Array { + throw UnsupportedOperationException() + } + + override fun getPresentableName(): String { + throw UnsupportedOperationException() + } + + override fun getUrls(p0: OrderRootType?): Array { + throw UnsupportedOperationException() + } + + override fun getOwnerModule(): Module { + throw UnsupportedOperationException() + } + + override fun accept(p0: RootPolicy?, p1: R?): R { + throw UnsupportedOperationException() + } + + override fun isValid(): Boolean { + throw UnsupportedOperationException() + } + + override fun getRootFiles(p0: OrderRootType?): Array? { + throw UnsupportedOperationException() + } + + override fun getRootUrls(p0: OrderRootType?): Array? { + throw UnsupportedOperationException() + } + + override fun compareTo(other: OrderEntry?): Int { + throw UnsupportedOperationException() + } + + override fun isSynthetic(): Boolean { + throw UnsupportedOperationException() + } + + } + + inner class MyModuleRootManager : ModuleRootManager() { + override fun getExcludeRoots(): Array { + throw UnsupportedOperationException() + } + + override fun getContentEntries(): Array { + throw UnsupportedOperationException() + } + + override fun getExcludeRootUrls(): Array { + throw UnsupportedOperationException() + } + + override fun processOrder(p0: RootPolicy?, p1: R): R { + throw UnsupportedOperationException() + } + + override fun getSourceRoots(p0: Boolean): Array { + throw UnsupportedOperationException() + } + + override fun getSourceRoots(): Array { + throw UnsupportedOperationException() + } + + override fun getSourceRoots(p0: JpsModuleSourceRootType<*>): MutableList { + throw UnsupportedOperationException() + } + + override fun getSourceRoots(p0: MutableSet>): MutableList { + throw UnsupportedOperationException() + } + + override fun getContentRoots(): Array { + throw UnsupportedOperationException() + } + + override fun orderEntries(): OrderEnumerator = + ProjectOrderEnumerator(project, null).using(object : RootModelProvider { + override fun getModules(): Array = arrayOf(module) + + override fun getRootModel(p0: Module): ModuleRootModel = this@MyModuleRootManager + }) + + override fun getModuleExtension(p0: Class?): T { + throw UnsupportedOperationException() + } + + override fun getDependencyModuleNames(): Array { + throw UnsupportedOperationException() + } + + override fun getModule(): Module = module + + override fun isSdkInherited(): Boolean { + throw UnsupportedOperationException() + } + + override fun getOrderEntries(): Array = arrayOf(moduleSourceOrderEntry, sdkOrderEntry) + + override fun getSourceRootUrls(): Array { + throw UnsupportedOperationException() + } + + override fun getSourceRootUrls(p0: Boolean): Array { + throw UnsupportedOperationException() + } + + override fun getSdk(): Sdk? { + throw UnsupportedOperationException() + } + + override fun getContentRootUrls(): Array { + throw UnsupportedOperationException() + } + + override fun getModuleDependencies(): Array { + throw UnsupportedOperationException() + } + + override fun getModuleDependencies(p0: Boolean): Array { + throw UnsupportedOperationException() + } + + override fun getModifiableModel(): ModifiableRootModel { + throw UnsupportedOperationException() + } + + override fun isDependsOn(p0: Module?): Boolean { + throw UnsupportedOperationException() + } + + override fun getFileIndex(): ModuleFileIndex { + return this@CoreProjectFileIndex + } + + override fun getDependencies(): Array { + throw UnsupportedOperationException() + } + + override fun getDependencies(p0: Boolean): Array { + throw UnsupportedOperationException() + } + } + + val moduleRootManager = MyModuleRootManager() + override fun getContentRootForFile(p0: VirtualFile): VirtualFile? { throw UnsupportedOperationException() } @@ -24,19 +416,15 @@ class CoreProjectFileIndex(): ProjectFileIndex { throw UnsupportedOperationException() } - override fun isInLibrarySource(p0: VirtualFile): Boolean { - throw UnsupportedOperationException() - } + override fun isInLibrarySource(file: VirtualFile): Boolean = false - override fun getClassRootForFile(p0: VirtualFile): VirtualFile? { - throw UnsupportedOperationException() - } + override fun getClassRootForFile(file: VirtualFile): VirtualFile? = + classpathRoots.firstOrNull { it.contains(file) }?.let { StandardFileSystems.local().findFileByPath(it.file.path) } - override fun getOrderEntriesForFile(p0: VirtualFile): List = emptyList() + override fun getOrderEntriesForFile(file: VirtualFile): List = + if (classpathRoots.contains(file)) listOf(sdkOrderEntry) else emptyList() - override fun isInLibraryClasses(p0: VirtualFile): Boolean { - throw UnsupportedOperationException() - } + override fun isInLibraryClasses(file: VirtualFile): Boolean = classpathRoots.contains(file) override fun isExcluded(p0: VirtualFile): Boolean { throw UnsupportedOperationException() @@ -54,7 +442,10 @@ class CoreProjectFileIndex(): ProjectFileIndex { throw UnsupportedOperationException() } - override fun getModuleForFile(p0: VirtualFile): Module? = null + override fun getModuleForFile(file: VirtualFile): Module? = + if (sourceRoots.contains(file)) module else null + + private fun List.contains(file: VirtualFile): Boolean = any { it.contains(file) } override fun getModuleForFile(p0: VirtualFile, p1: Boolean): Module? { throw UnsupportedOperationException() @@ -72,7 +463,7 @@ class CoreProjectFileIndex(): ProjectFileIndex { throw UnsupportedOperationException() } - override fun isInSourceContent(p0: VirtualFile): Boolean = false + override fun isInSourceContent(file: VirtualFile): Boolean = sourceRoots.contains(file) override fun iterateContent(p0: ContentIterator): Boolean { throw UnsupportedOperationException() @@ -86,12 +477,74 @@ class CoreProjectFileIndex(): ProjectFileIndex { throw UnsupportedOperationException() } - override fun isInTestSourceContent(p0: VirtualFile): Boolean { + override fun isInTestSourceContent(file: VirtualFile): Boolean = false + + override fun isUnderSourceRootOfType(p0: VirtualFile, p1: MutableSet>): Boolean { throw UnsupportedOperationException() } - override fun isUnderSourceRootOfType(p0: VirtualFile, p1: MutableSet>): Boolean { + override fun getOrderEntryForFile(p0: VirtualFile): OrderEntry? { + throw UnsupportedOperationException() + } +} + +class CoreProjectRootManager(val projectFileIndex: CoreProjectFileIndex) : ProjectRootManager() { + override fun orderEntries(): OrderEnumerator { + throw UnsupportedOperationException() + } + + override fun orderEntries(p0: MutableCollection): OrderEnumerator { throw UnsupportedOperationException() } + + override fun getContentRootsFromAllModules(): Array? { + throw UnsupportedOperationException() + } + + override fun setProjectSdk(p0: Sdk?) { + throw UnsupportedOperationException() + } + + override fun setProjectSdkName(p0: String?) { + throw UnsupportedOperationException() + } + + override fun getModuleSourceRoots(p0: MutableSet>): MutableList { + throw UnsupportedOperationException() + } + + override fun getContentSourceRoots(): Array { + throw UnsupportedOperationException() + } + + override fun getFileIndex(): ProjectFileIndex = projectFileIndex + + override fun getProjectSdkName(): String? { + throw UnsupportedOperationException() + } + + override fun getProjectSdk(): Sdk? { + throw UnsupportedOperationException() + } + + override fun getContentRoots(): Array { + throw UnsupportedOperationException() + } + + override fun getContentRootUrls(): MutableList { + throw UnsupportedOperationException() + } + } +fun ContentRoot.contains(file: VirtualFile) = when (this) { + is JvmContentRoot -> { + val path = if (file.fileSystem.protocol == StandardFileSystems.JAR_PROTOCOL) + StandardFileSystems.getVirtualFileForJar(file)?.path ?: file.path + else + file.path + File(path).startsWith(this.file.absoluteFile) + } + is KotlinSourceRoot -> File(file.path).startsWith(File(this.path).absoluteFile) + else -> false +} diff --git a/src/Java/JavaDocumentationBuilder.kt b/src/Java/JavaDocumentationBuilder.kt deleted file mode 100644 index e0cc53a0..00000000 --- a/src/Java/JavaDocumentationBuilder.kt +++ /dev/null @@ -1,422 +0,0 @@ -package org.jetbrains.dokka - -import com.intellij.psi.* -import com.intellij.psi.javadoc.PsiDocTag -import com.intellij.psi.javadoc.PsiDocTagValue -import com.intellij.psi.javadoc.PsiDocToken -import com.intellij.psi.javadoc.PsiInlineDocTag -import org.jetbrains.dokka.DocumentationNode.Kind -import org.jsoup.Jsoup -import org.jsoup.nodes.Element -import org.jsoup.nodes.Node -import org.jsoup.nodes.TextNode - -data class JavadocParseResult(val content: Content, val deprecatedContent: Content?) { - companion object { - val Empty = JavadocParseResult(Content.Empty, null) - } -} - -interface JavaDocumentationParser { - fun parseDocumentation(element: PsiNamedElement): JavadocParseResult -} - -class JavadocParser(private val refGraph: NodeReferenceGraph) : JavaDocumentationParser { - override fun parseDocumentation(element: PsiNamedElement): JavadocParseResult { - val docComment = (element as? PsiDocCommentOwner)?.docComment - if (docComment == null) return JavadocParseResult.Empty - val result = MutableContent() - var deprecatedContent: Content? = null - val para = ContentParagraph() - result.append(para) - para.convertJavadocElements(docComment.descriptionElements.dropWhile { it.text.trim().isEmpty() }) - docComment.tags.forEach { tag -> - when(tag.name) { - "see" -> result.convertSeeTag(tag) - "deprecated" -> { - deprecatedContent = Content() - deprecatedContent!!.convertJavadocElements(tag.contentElements()) - } - else -> { - val subjectName = tag.getSubjectName() - val section = result.addSection(javadocSectionDisplayName(tag.name), subjectName) - - section.convertJavadocElements(tag.contentElements()) - } - } - } - return JavadocParseResult(result, deprecatedContent) - } - - 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 ContentBlock.convertJavadocElements(elements: Iterable) { - val htmlBuilder = StringBuilder() - elements.forEach { - if (it is PsiInlineDocTag) { - htmlBuilder.append(convertInlineDocTag(it)) - } else { - htmlBuilder.append(it.text) - } - } - val doc = Jsoup.parse(htmlBuilder.toString().trimStart()) - doc.body().childNodes().forEach { - convertHtmlNode(it) - } - } - - private fun ContentBlock.convertHtmlNode(node: Node) { - if (node is TextNode) { - append(ContentText(node.text())) - } else if (node is Element) { - val childBlock = createBlock(node) - node.childNodes().forEach { - childBlock.convertHtmlNode(it) - } - append(childBlock) - } - } - - private fun createBlock(element: Element): ContentBlock = when(element.tagName()) { - "p" -> ContentParagraph() - "b", "strong" -> ContentStrong() - "i", "em" -> ContentEmphasis() - "s", "del" -> ContentStrikethrough() - "code" -> ContentCode() - "pre" -> ContentBlockCode() - "ul" -> ContentUnorderedList() - "ol" -> ContentOrderedList() - "li" -> ContentListItem() - "a" -> createLink(element) - else -> ContentBlock() - } - - private fun createLink(element: Element): ContentBlock { - val docref = element.attr("docref") - if (docref != null) { - return ContentNodeLazyLink(docref, { -> refGraph.lookup(docref)}) - } - val href = element.attr("href") - if (href != null) { - return ContentExternalLink(href) - } else { - return ContentBlock() - } - } - - private fun MutableContent.convertSeeTag(tag: PsiDocTag) { - val linkElement = tag.linkElement() - if (linkElement == null) { - return - } - val seeSection = findSectionByTag(ContentTags.SeeAlso) ?: addSection(ContentTags.SeeAlso, null) - val linkSignature = resolveLink(linkElement) - val text = ContentText(linkElement.text) - if (linkSignature != null) { - val linkNode = ContentNodeLazyLink(tag.valueElement!!.text, { -> refGraph.lookup(linkSignature)}) - linkNode.append(text) - seeSection.append(linkNode) - } else { - seeSection.append(text) - } - } - - private fun convertInlineDocTag(tag: PsiInlineDocTag) = when (tag.name) { - "link", "linkplain" -> { - val valueElement = tag.linkElement() - val linkSignature = resolveLink(valueElement) - if (linkSignature != null) { - val labelText = tag.dataElements.firstOrNull { it is PsiDocToken }?.text ?: valueElement!!.text - val link = "${labelText.htmlEscape()}" - 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().htmlEscape() - if (tag.name == "code") "$escaped" else escaped - } - else -> tag.text - } - - private fun PsiDocTag.linkElement(): PsiElement? = - valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace } - - private fun resolveLink(valueElement: PsiElement?): String? { - val target = valueElement?.reference?.resolve() - if (target != null) { - return getSignature(target) - } - return null - } - - fun PsiDocTag.getSubjectName(): String? { - if (name == "param" || name == "throws" || name == "exception") { - return valueElement?.text - } - return null - } -} - - -private fun getSignature(element: PsiElement?) = when(element) { - is PsiClass -> element.qualifiedName - is PsiField -> element.containingClass!!.qualifiedName + "#" + element.name - is PsiMethod -> - element.containingClass!!.qualifiedName + "#" + element.name + "(" + - element.parameterList.parameters.map { it.type.typeSignature() }.joinToString(",") + ")" - else -> null -} - -private fun PsiType.typeSignature(): String = when(this) { - is PsiArrayType -> "Array<${componentType.typeSignature()}>" - else -> mapTypeName(this) -} - -private fun mapTypeName(psiType: PsiType): String = when (psiType) { - PsiType.VOID -> "Unit" - is PsiPrimitiveType -> psiType.canonicalText.capitalize() - is PsiClassType -> { - val psiClass = psiType.resolve() - if (psiClass?.qualifiedName == "java.lang.Object") "Any" else psiType.className - } - is PsiEllipsisType -> mapTypeName(psiType.componentType) - is PsiArrayType -> "Array" - else -> psiType.canonicalText -} - -class JavaDocumentationBuilder(private val options: DocumentationOptions, - private val refGraph: NodeReferenceGraph, - private val docParser: JavaDocumentationParser = JavadocParser(refGraph)) { - fun appendFile(file: PsiJavaFile, module: DocumentationModule) { - if (file.classes.all { skipElement(it) }) { - return - } - val packageNode = module.findOrCreatePackageNode(file.packageName, emptyMap()) - appendClasses(packageNode, file.classes) - } - - fun appendClasses(packageNode: DocumentationNode, classes: Array) { - packageNode.appendChildren(classes) { build() } - } - - fun register(element: PsiElement, node: DocumentationNode) { - val signature = getSignature(element) - if (signature != null) { - refGraph.register(signature, node) - } - } - - fun link(node: DocumentationNode, element: PsiElement?) { - val qualifiedName = getSignature(element) - if (qualifiedName != null) { - refGraph.link(node, qualifiedName, DocumentationReference.Kind.Link) - } - } - - fun link(element: PsiElement?, node: DocumentationNode, kind: DocumentationReference.Kind) { - val qualifiedName = getSignature(element) - if (qualifiedName != null) { - refGraph.link(qualifiedName, node, kind) - } - } - fun DocumentationNode(element: PsiNamedElement, - kind: Kind, - name: String = element.name ?: ""): DocumentationNode { - val (docComment, deprecatedContent) = docParser.parseDocumentation(element) - val node = DocumentationNode(name, docComment, kind) - if (element is PsiModifierListOwner) { - node.appendModifiers(element) - val modifierList = element.modifierList - if (modifierList != null) { - modifierList.annotations.filter { !ignoreAnnotation(it) }.forEach { - val annotation = it.build() - node.append(annotation, - if (it.qualifiedName == "java.lang.Deprecated") DocumentationReference.Kind.Deprecation else DocumentationReference.Kind.Annotation) - } - } - } - if (deprecatedContent != null) { - val deprecationNode = DocumentationNode("", deprecatedContent, Kind.Modifier) - node.append(deprecationNode, DocumentationReference.Kind.Deprecation) - } - return node - } - - fun ignoreAnnotation(annotation: PsiAnnotation) = when(annotation.qualifiedName) { - "java.lang.SuppressWarnings" -> true - else -> false - } - - fun DocumentationNode.appendChildren(elements: Array, - kind: DocumentationReference.Kind = DocumentationReference.Kind.Member, - buildFn: T.() -> DocumentationNode) { - elements.forEach { - if (!skipElement(it)) { - append(it.buildFn(), kind) - } - } - } - - private fun skipElement(element: Any) = skipElementByVisibility(element) || hasSuppressTag(element) - - private fun skipElementByVisibility(element: Any): Boolean = - !options.includeNonPublic && element is PsiModifierListOwner && - (element.hasModifierProperty(PsiModifier.PRIVATE) || element.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) - - private fun hasSuppressTag(element: Any) = - element is PsiDocCommentOwner && element.docComment?.let { it.findTagByName("suppress") != null } ?: false - - fun DocumentationNode.appendMembers(elements: Array, buildFn: T.() -> DocumentationNode) = - appendChildren(elements, DocumentationReference.Kind.Member, buildFn) - - fun DocumentationNode.appendDetails(elements: Array, buildFn: T.() -> DocumentationNode) = - appendChildren(elements, DocumentationReference.Kind.Detail, buildFn) - - fun PsiClass.build(): DocumentationNode { - val kind = when { - isInterface -> DocumentationNode.Kind.Interface - isEnum -> DocumentationNode.Kind.Enum - isAnnotationType -> DocumentationNode.Kind.AnnotationClass - else -> DocumentationNode.Kind.Class - } - val node = DocumentationNode(this, kind) - superTypes.filter { !ignoreSupertype(it) }.forEach { - node.appendType(it, Kind.Supertype) - val superClass = it.resolve() - if (superClass != null) { - link(superClass, node, DocumentationReference.Kind.Inheritor) - } - } - node.appendDetails(typeParameters) { build() } - node.appendMembers(methods) { build() } - node.appendMembers(fields) { build() } - node.appendMembers(innerClasses) { build() } - register(this, node) - return node - } - - fun ignoreSupertype(psiType: PsiClassType): Boolean = - psiType.isClass("java.lang.Enum") || psiType.isClass("java.lang.Object") - - fun PsiClassType.isClass(qName: String): Boolean { - val shortName = qName.substringAfterLast('.') - if (className == shortName) { - val psiClass = resolve() - return psiClass?.qualifiedName == qName - } - return false - } - - fun PsiField.build(): DocumentationNode { - val node = DocumentationNode(this, nodeKind()) - if (!hasModifierProperty(PsiModifier.FINAL)) { - node.appendTextNode("var", Kind.Modifier) - } - node.appendType(type) - register(this, node) - return node - } - - private fun PsiField.nodeKind(): Kind = when { - this is PsiEnumConstant -> Kind.EnumItem - hasModifierProperty(PsiModifier.STATIC) -> Kind.CompanionObjectProperty - else -> Kind.Property - } - - fun PsiMethod.build(): DocumentationNode { - val node = DocumentationNode(this, nodeKind(), - if (isConstructor) "" else name) - - if (!isConstructor) { - node.appendType(returnType) - } - node.appendDetails(parameterList.parameters) { build() } - node.appendDetails(typeParameters) { build() } - register(this, node) - return node - } - - private fun PsiMethod.nodeKind(): Kind = when { - isConstructor -> Kind.Constructor - hasModifierProperty(PsiModifier.STATIC) -> Kind.CompanionObjectFunction - else -> Kind.Function - } - - fun PsiParameter.build(): DocumentationNode { - val node = DocumentationNode(this, Kind.Parameter) - node.appendType(type) - if (type is PsiEllipsisType) { - node.appendTextNode("vararg", Kind.Annotation, DocumentationReference.Kind.Annotation) - } - return node - } - - fun PsiTypeParameter.build(): DocumentationNode { - val node = DocumentationNode(this, Kind.TypeParameter) - extendsListTypes.forEach { node.appendType(it, Kind.UpperBound) } - implementsListTypes.forEach { node.appendType(it, Kind.UpperBound) } - return node - } - - fun DocumentationNode.appendModifiers(element: PsiModifierListOwner) { - val modifierList = element.modifierList ?: return - - PsiModifier.MODIFIERS.forEach { - if (it != "static" && modifierList.hasExplicitModifier(it)) { - appendTextNode(it, Kind.Modifier) - } - } - if ((element is PsiClass || (element is PsiMethod && !element.isConstructor)) && - !element.hasModifierProperty(PsiModifier.FINAL)) { - appendTextNode("open", Kind.Modifier) - } - } - - fun DocumentationNode.appendType(psiType: PsiType?, kind: DocumentationNode.Kind = DocumentationNode.Kind.Type) { - if (psiType == null) { - return - } - append(psiType.build(kind), DocumentationReference.Kind.Detail) - } - - fun PsiType.build(kind: DocumentationNode.Kind = DocumentationNode.Kind.Type): DocumentationNode { - val name = mapTypeName(this) - val node = DocumentationNode(name, Content.Empty, kind) - if (this is PsiClassType) { - node.appendDetails(parameters) { build(Kind.Type) } - link(node, resolve()) - } - if (this is PsiArrayType && this !is PsiEllipsisType) { - node.append(componentType.build(Kind.Type), DocumentationReference.Kind.Detail) - } - return node - } - - fun PsiAnnotation.build(): DocumentationNode { - val node = DocumentationNode(nameReferenceElement?.text ?: "", Content.Empty, DocumentationNode.Kind.Annotation) - parameterList.attributes.forEach { - val parameter = DocumentationNode(it.name ?: "value", Content.Empty, DocumentationNode.Kind.Parameter) - val value = it.value - if (value != null) { - val valueText = (value as? PsiLiteralExpression)?.value as? String ?: value.text - val valueNode = DocumentationNode(valueText, Content.Empty, DocumentationNode.Kind.Value) - parameter.append(valueNode, DocumentationReference.Kind.Detail) - } - node.append(parameter, DocumentationReference.Kind.Detail) - } - return node - } -} diff --git a/src/Java/JavaPsiDocumentationBuilder.kt b/src/Java/JavaPsiDocumentationBuilder.kt new file mode 100644 index 00000000..a5ee891e --- /dev/null +++ b/src/Java/JavaPsiDocumentationBuilder.kt @@ -0,0 +1,426 @@ +package org.jetbrains.dokka + +import com.intellij.psi.* +import com.intellij.psi.javadoc.PsiDocTag +import com.intellij.psi.javadoc.PsiDocTagValue +import com.intellij.psi.javadoc.PsiDocToken +import com.intellij.psi.javadoc.PsiInlineDocTag +import org.jetbrains.dokka.DocumentationNode.Kind +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import org.jsoup.nodes.Node +import org.jsoup.nodes.TextNode + +data class JavadocParseResult(val content: Content, val deprecatedContent: Content?) { + companion object { + val Empty = JavadocParseResult(Content.Empty, null) + } +} + +interface JavaDocumentationParser { + fun parseDocumentation(element: PsiNamedElement): JavadocParseResult +} + +class JavadocParser(private val refGraph: NodeReferenceGraph) : JavaDocumentationParser { + override fun parseDocumentation(element: PsiNamedElement): JavadocParseResult { + val docComment = (element as? PsiDocCommentOwner)?.docComment + if (docComment == null) return JavadocParseResult.Empty + val result = MutableContent() + var deprecatedContent: Content? = null + val para = ContentParagraph() + result.append(para) + para.convertJavadocElements(docComment.descriptionElements.dropWhile { it.text.trim().isEmpty() }) + docComment.tags.forEach { tag -> + when(tag.name) { + "see" -> result.convertSeeTag(tag) + "deprecated" -> { + deprecatedContent = Content() + deprecatedContent!!.convertJavadocElements(tag.contentElements()) + } + else -> { + val subjectName = tag.getSubjectName() + val section = result.addSection(javadocSectionDisplayName(tag.name), subjectName) + + section.convertJavadocElements(tag.contentElements()) + } + } + } + return JavadocParseResult(result, deprecatedContent) + } + + 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 ContentBlock.convertJavadocElements(elements: Iterable) { + val htmlBuilder = StringBuilder() + elements.forEach { + if (it is PsiInlineDocTag) { + htmlBuilder.append(convertInlineDocTag(it)) + } else { + htmlBuilder.append(it.text) + } + } + val doc = Jsoup.parse(htmlBuilder.toString().trimStart()) + doc.body().childNodes().forEach { + convertHtmlNode(it) + } + } + + private fun ContentBlock.convertHtmlNode(node: Node) { + if (node is TextNode) { + append(ContentText(node.text())) + } else if (node is Element) { + val childBlock = createBlock(node) + node.childNodes().forEach { + childBlock.convertHtmlNode(it) + } + append(childBlock) + } + } + + private fun createBlock(element: Element): ContentBlock = when(element.tagName()) { + "p" -> ContentParagraph() + "b", "strong" -> ContentStrong() + "i", "em" -> ContentEmphasis() + "s", "del" -> ContentStrikethrough() + "code" -> ContentCode() + "pre" -> ContentBlockCode() + "ul" -> ContentUnorderedList() + "ol" -> ContentOrderedList() + "li" -> ContentListItem() + "a" -> createLink(element) + else -> ContentBlock() + } + + private fun createLink(element: Element): ContentBlock { + val docref = element.attr("docref") + if (docref != null) { + return ContentNodeLazyLink(docref, { -> refGraph.lookup(docref)}) + } + val href = element.attr("href") + if (href != null) { + return ContentExternalLink(href) + } else { + return ContentBlock() + } + } + + private fun MutableContent.convertSeeTag(tag: PsiDocTag) { + val linkElement = tag.linkElement() + if (linkElement == null) { + return + } + val seeSection = findSectionByTag(ContentTags.SeeAlso) ?: addSection(ContentTags.SeeAlso, null) + val linkSignature = resolveLink(linkElement) + val text = ContentText(linkElement.text) + if (linkSignature != null) { + val linkNode = ContentNodeLazyLink(tag.valueElement!!.text, { -> refGraph.lookup(linkSignature)}) + linkNode.append(text) + seeSection.append(linkNode) + } else { + seeSection.append(text) + } + } + + private fun convertInlineDocTag(tag: PsiInlineDocTag) = when (tag.name) { + "link", "linkplain" -> { + val valueElement = tag.linkElement() + val linkSignature = resolveLink(valueElement) + if (linkSignature != null) { + val labelText = tag.dataElements.firstOrNull { it is PsiDocToken }?.text ?: valueElement!!.text + val link = "${labelText.htmlEscape()}" + 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().htmlEscape() + if (tag.name == "code") "$escaped" else escaped + } + else -> tag.text + } + + private fun PsiDocTag.linkElement(): PsiElement? = + valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace } + + private fun resolveLink(valueElement: PsiElement?): String? { + val target = valueElement?.reference?.resolve() + if (target != null) { + return getSignature(target) + } + return null + } + + fun PsiDocTag.getSubjectName(): String? { + if (name == "param" || name == "throws" || name == "exception") { + return valueElement?.text + } + return null + } +} + + +private fun getSignature(element: PsiElement?) = when(element) { + is PsiClass -> element.qualifiedName + is PsiField -> element.containingClass!!.qualifiedName + "#" + element.name + is PsiMethod -> + element.containingClass!!.qualifiedName + "#" + element.name + "(" + + element.parameterList.parameters.map { it.type.typeSignature() }.joinToString(",") + ")" + else -> null +} + +private fun PsiType.typeSignature(): String = when(this) { + is PsiArrayType -> "Array<${componentType.typeSignature()}>" + else -> mapTypeName(this) +} + +private fun mapTypeName(psiType: PsiType): String = when (psiType) { + PsiType.VOID -> "Unit" + is PsiPrimitiveType -> psiType.canonicalText.capitalize() + is PsiClassType -> { + val psiClass = psiType.resolve() + if (psiClass?.qualifiedName == "java.lang.Object") "Any" else psiType.className + } + is PsiEllipsisType -> mapTypeName(psiType.componentType) + is PsiArrayType -> "Array" + else -> psiType.canonicalText +} + +interface JavaDocumentationBuilder { + fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map) +} + +class JavaPsiDocumentationBuilder(private val options: DocumentationOptions, + private val refGraph: NodeReferenceGraph, + private val docParser: JavaDocumentationParser = JavadocParser(refGraph)) : JavaDocumentationBuilder { + override fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map) { + if (file.classes.all { skipElement(it) }) { + return + } + val packageNode = module.findOrCreatePackageNode(file.packageName, emptyMap()) + appendClasses(packageNode, file.classes) + } + + fun appendClasses(packageNode: DocumentationNode, classes: Array) { + packageNode.appendChildren(classes) { build() } + } + + fun register(element: PsiElement, node: DocumentationNode) { + val signature = getSignature(element) + if (signature != null) { + refGraph.register(signature, node) + } + } + + fun link(node: DocumentationNode, element: PsiElement?) { + val qualifiedName = getSignature(element) + if (qualifiedName != null) { + refGraph.link(node, qualifiedName, DocumentationReference.Kind.Link) + } + } + + fun link(element: PsiElement?, node: DocumentationNode, kind: DocumentationReference.Kind) { + val qualifiedName = getSignature(element) + if (qualifiedName != null) { + refGraph.link(qualifiedName, node, kind) + } + } + fun DocumentationNode(element: PsiNamedElement, + kind: Kind, + name: String = element.name ?: ""): DocumentationNode { + val (docComment, deprecatedContent) = docParser.parseDocumentation(element) + val node = DocumentationNode(name, docComment, kind) + if (element is PsiModifierListOwner) { + node.appendModifiers(element) + val modifierList = element.modifierList + if (modifierList != null) { + modifierList.annotations.filter { !ignoreAnnotation(it) }.forEach { + val annotation = it.build() + node.append(annotation, + if (it.qualifiedName == "java.lang.Deprecated") DocumentationReference.Kind.Deprecation else DocumentationReference.Kind.Annotation) + } + } + } + if (deprecatedContent != null) { + val deprecationNode = DocumentationNode("", deprecatedContent, Kind.Modifier) + node.append(deprecationNode, DocumentationReference.Kind.Deprecation) + } + return node + } + + fun ignoreAnnotation(annotation: PsiAnnotation) = when(annotation.qualifiedName) { + "java.lang.SuppressWarnings" -> true + else -> false + } + + fun DocumentationNode.appendChildren(elements: Array, + kind: DocumentationReference.Kind = DocumentationReference.Kind.Member, + buildFn: T.() -> DocumentationNode) { + elements.forEach { + if (!skipElement(it)) { + append(it.buildFn(), kind) + } + } + } + + private fun skipElement(element: Any) = skipElementByVisibility(element) || hasSuppressTag(element) + + private fun skipElementByVisibility(element: Any): Boolean = + !options.includeNonPublic && element is PsiModifierListOwner && + (element.hasModifierProperty(PsiModifier.PRIVATE) || element.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) + + private fun hasSuppressTag(element: Any) = + element is PsiDocCommentOwner && element.docComment?.let { it.findTagByName("suppress") != null } ?: false + + fun DocumentationNode.appendMembers(elements: Array, buildFn: T.() -> DocumentationNode) = + appendChildren(elements, DocumentationReference.Kind.Member, buildFn) + + fun DocumentationNode.appendDetails(elements: Array, buildFn: T.() -> DocumentationNode) = + appendChildren(elements, DocumentationReference.Kind.Detail, buildFn) + + fun PsiClass.build(): DocumentationNode { + val kind = when { + isInterface -> DocumentationNode.Kind.Interface + isEnum -> DocumentationNode.Kind.Enum + isAnnotationType -> DocumentationNode.Kind.AnnotationClass + else -> DocumentationNode.Kind.Class + } + val node = DocumentationNode(this, kind) + superTypes.filter { !ignoreSupertype(it) }.forEach { + node.appendType(it, Kind.Supertype) + val superClass = it.resolve() + if (superClass != null) { + link(superClass, node, DocumentationReference.Kind.Inheritor) + } + } + node.appendDetails(typeParameters) { build() } + node.appendMembers(methods) { build() } + node.appendMembers(fields) { build() } + node.appendMembers(innerClasses) { build() } + register(this, node) + return node + } + + fun ignoreSupertype(psiType: PsiClassType): Boolean = + psiType.isClass("java.lang.Enum") || psiType.isClass("java.lang.Object") + + fun PsiClassType.isClass(qName: String): Boolean { + val shortName = qName.substringAfterLast('.') + if (className == shortName) { + val psiClass = resolve() + return psiClass?.qualifiedName == qName + } + return false + } + + fun PsiField.build(): DocumentationNode { + val node = DocumentationNode(this, nodeKind()) + if (!hasModifierProperty(PsiModifier.FINAL)) { + node.appendTextNode("var", Kind.Modifier) + } + node.appendType(type) + register(this, node) + return node + } + + private fun PsiField.nodeKind(): Kind = when { + this is PsiEnumConstant -> Kind.EnumItem + hasModifierProperty(PsiModifier.STATIC) -> Kind.CompanionObjectProperty + else -> Kind.Property + } + + fun PsiMethod.build(): DocumentationNode { + val node = DocumentationNode(this, nodeKind(), + if (isConstructor) "" else name) + + if (!isConstructor) { + node.appendType(returnType) + } + node.appendDetails(parameterList.parameters) { build() } + node.appendDetails(typeParameters) { build() } + register(this, node) + return node + } + + private fun PsiMethod.nodeKind(): Kind = when { + isConstructor -> Kind.Constructor + hasModifierProperty(PsiModifier.STATIC) -> Kind.CompanionObjectFunction + else -> Kind.Function + } + + fun PsiParameter.build(): DocumentationNode { + val node = DocumentationNode(this, Kind.Parameter) + node.appendType(type) + if (type is PsiEllipsisType) { + node.appendTextNode("vararg", Kind.Annotation, DocumentationReference.Kind.Annotation) + } + return node + } + + fun PsiTypeParameter.build(): DocumentationNode { + val node = DocumentationNode(this, Kind.TypeParameter) + extendsListTypes.forEach { node.appendType(it, Kind.UpperBound) } + implementsListTypes.forEach { node.appendType(it, Kind.UpperBound) } + return node + } + + fun DocumentationNode.appendModifiers(element: PsiModifierListOwner) { + val modifierList = element.modifierList ?: return + + PsiModifier.MODIFIERS.forEach { + if (it != "static" && modifierList.hasExplicitModifier(it)) { + appendTextNode(it, Kind.Modifier) + } + } + if ((element is PsiClass || (element is PsiMethod && !element.isConstructor)) && + !element.hasModifierProperty(PsiModifier.FINAL)) { + appendTextNode("open", Kind.Modifier) + } + } + + fun DocumentationNode.appendType(psiType: PsiType?, kind: DocumentationNode.Kind = DocumentationNode.Kind.Type) { + if (psiType == null) { + return + } + append(psiType.build(kind), DocumentationReference.Kind.Detail) + } + + fun PsiType.build(kind: DocumentationNode.Kind = DocumentationNode.Kind.Type): DocumentationNode { + val name = mapTypeName(this) + val node = DocumentationNode(name, Content.Empty, kind) + if (this is PsiClassType) { + node.appendDetails(parameters) { build(Kind.Type) } + link(node, resolve()) + } + if (this is PsiArrayType && this !is PsiEllipsisType) { + node.append(componentType.build(Kind.Type), DocumentationReference.Kind.Detail) + } + return node + } + + fun PsiAnnotation.build(): DocumentationNode { + val node = DocumentationNode(nameReferenceElement?.text ?: "", Content.Empty, DocumentationNode.Kind.Annotation) + parameterList.attributes.forEach { + val parameter = DocumentationNode(it.name ?: "value", Content.Empty, DocumentationNode.Kind.Parameter) + val value = it.value + if (value != null) { + val valueText = (value as? PsiLiteralExpression)?.value as? String ?: value.text + val valueNode = DocumentationNode(valueText, Content.Empty, DocumentationNode.Kind.Value) + parameter.append(valueNode, DocumentationReference.Kind.Detail) + } + node.append(parameter, DocumentationReference.Kind.Detail) + } + return node + } +} diff --git a/src/Kotlin/DocumentationBuilder.kt b/src/Kotlin/DocumentationBuilder.kt index b5b079de..7dc4075b 100644 --- a/src/Kotlin/DocumentationBuilder.kt +++ b/src/Kotlin/DocumentationBuilder.kt @@ -1,7 +1,9 @@ package org.jetbrains.dokka -import com.intellij.openapi.project.Project import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.PsiDocCommentOwner +import com.intellij.psi.PsiJavaFile +import com.intellij.psi.PsiNamedElement import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.dokka.DocumentationNode.Kind import org.jetbrains.kotlin.builtins.KotlinBuiltIns @@ -9,6 +11,8 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.KotlinCacheService +import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfo import org.jetbrains.kotlin.idea.kdoc.KDocFinder import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink import org.jetbrains.kotlin.idea.resolve.ResolutionFacade @@ -16,6 +20,9 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor +import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor +import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtModifierListOwner import org.jetbrains.kotlin.psi.KtParameter @@ -25,6 +32,8 @@ import org.jetbrains.kotlin.resolve.constants.ConstantValue import org.jetbrains.kotlin.resolve.constants.TypedCompileTimeConstant import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.isDocumentedAnnotation +import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform import org.jetbrains.kotlin.resolve.lazy.ResolveSession import org.jetbrains.kotlin.resolve.source.PsiSourceElement import org.jetbrains.kotlin.resolve.source.getPsi @@ -55,7 +64,7 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, val session: ResolveSession, val options: DocumentationOptions, val refGraph: NodeReferenceGraph, - val logger: DokkaLogger) { + val logger: DokkaLogger) : JavaDocumentationBuilder { val visibleToDocumentation = setOf(Visibilities.PROTECTED, Visibilities.PUBLIC) val boringBuiltinClasses = setOf( "kotlin.Unit", "kotlin.Byte", "kotlin.Short", "kotlin.Int", "kotlin.Long", "kotlin.Char", "kotlin.Boolean", @@ -65,7 +74,14 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, KtTokens.OPEN_KEYWORD, KtTokens.FINAL_KEYWORD, KtTokens.ABSTRACT_KEYWORD, KtTokens.SEALED_KEYWORD, KtTokens.OVERRIDE_KEYWORD) - fun parseDocumentation(descriptor: DeclarationDescriptor): Content { + + fun parseDocumentation(descriptor: DeclarationDescriptor): Content = parseDocumentationAndDetails(descriptor).first + + fun parseDocumentationAndDetails(descriptor: DeclarationDescriptor): Pair Unit> { + if (descriptor is JavaClassDescriptor || descriptor is JavaCallableMemberDescriptor) { + return parseJavadoc(descriptor) + } + val kdoc = KDocFinder.findKDoc(descriptor) ?: findStdlibKDoc(descriptor) if (kdoc == null) { if (options.reportUndocumented && !descriptor.isDeprecated() && @@ -73,7 +89,7 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, descriptor !is PropertyAccessorDescriptor) { logger.warn("No documentation for ${descriptor.signatureWithSourceLocation()}") } - return Content.Empty + return Content.Empty to { node -> } } var kdocText = kdoc.getContent() // workaround for code fence parsing problem in IJ markdown parser @@ -100,7 +116,7 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, } } } - return content + return content to { node -> } } /** @@ -132,6 +148,20 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, return null } + fun parseJavadoc(descriptor: DeclarationDescriptor): Pair Unit> { + val psi = ((descriptor as? DeclarationDescriptorWithSource)?.source as? PsiSourceElement)?.psi + if (psi is PsiDocCommentOwner) { + val parseResult = JavadocParser(refGraph).parseDocumentation(psi as PsiNamedElement) + return parseResult.content to { node -> + parseResult.deprecatedContent?.let { + val deprecationNode = DocumentationNode("", it, DocumentationNode.Kind.Modifier) + node.append(deprecationNode, DocumentationReference.Kind.Deprecation) + } + } + } + return Content.Empty to { node -> } + } + fun DeclarationDescriptor.signature(): String = when(this) { is ClassDescriptor, is PackageFragmentDescriptor -> DescriptorUtils.getFqName(this).asString() is PropertyDescriptor -> containingDeclaration.signature() + "#" + name + receiverSignature() @@ -246,8 +276,9 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, } fun DocumentationNode(descriptor: T, kind: Kind): DocumentationNode where T : DeclarationDescriptor, T : Named { - val doc = parseDocumentation(descriptor) + val (doc, callback) = parseDocumentationAndDetails(descriptor) val node = DocumentationNode(descriptor.name.asString(), doc, kind).withModifiers(descriptor) + callback(node) return node } @@ -274,7 +305,7 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, } fun DocumentationNode.appendVisibility(descriptor: DeclarationDescriptorWithVisibility) { - val modifier = descriptor.visibility.toString() + val modifier = descriptor.visibility.normalize().displayName appendTextNode(modifier, DocumentationNode.Kind.Modifier) } @@ -393,18 +424,20 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, (!options.skipDeprecated || !isDeprecated()) } - fun DocumentationNode.appendMembers(descriptors: Iterable) { - descriptors.forEach { descriptor -> + fun DocumentationNode.appendMembers(descriptors: Iterable): List { + val nodes = descriptors.map { descriptor -> if (descriptor is CallableMemberDescriptor && descriptor.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) { val baseDescriptor = descriptor.overriddenDescriptors.firstOrNull() if (baseDescriptor != null) { link(this, baseDescriptor, DocumentationReference.Kind.InheritedMember) } + null } else { appendChild(descriptor, DocumentationReference.Kind.Member) } } + return nodes.filterNotNull() } fun DocumentationNode.appendInPageChildren(descriptors: Iterable, kind: DocumentationReference.Kind) { @@ -464,6 +497,9 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, } val members = defaultType.memberScope.getAllDescriptors().filter { it != companionObjectDescriptor } node.appendMembers(members) + node.appendMembers(staticScope.getDescriptors()).forEach { + it.appendTextNode("static", Kind.Modifier) + } val companionObjectDescriptor = companionObjectDescriptor if (companionObjectDescriptor != null) { node.appendMembers(companionObjectDescriptor.defaultType.memberScope.getAllDescriptors()) @@ -583,6 +619,9 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, } node.appendAnnotations(this) node.appendModifiers(this) + if (varargElementType != null && node.details(Kind.Modifier).none { it.name == "vararg" }) { + node.appendTextNode("vararg", Kind.Modifier) + } register(this, node) return node } @@ -678,6 +717,23 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, } } } + + override fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map) { + val packageNode = module.findOrCreatePackageNode(file.packageName, packageContent) + + file.classes.forEach { + val javaDescriptorResolver = KotlinCacheService.getInstance(file.project).getProjectService(JvmPlatform, + it.getModuleInfo(), javaClass()) + + val descriptor = javaDescriptorResolver.resolveClass(JavaClassImpl(it)) + if (descriptor == null) { + logger.warn("Cannot find descriptor for Java class ${it.qualifiedName}") + } + else { + packageNode.appendChild(descriptor, DocumentationReference.Kind.Member) + } + } + } } private fun AnnotationDescriptor.isDocumented(): Boolean { diff --git a/src/Kotlin/KotlinAsJavaDocumentationBuilder.kt b/src/Kotlin/KotlinAsJavaDocumentationBuilder.kt index a32e9a1f..1bd930e2 100644 --- a/src/Kotlin/KotlinAsJavaDocumentationBuilder.kt +++ b/src/Kotlin/KotlinAsJavaDocumentationBuilder.kt @@ -20,7 +20,7 @@ class KotlinAsJavaDocumentationBuilder() : PackageDocumentationBuilder { return } - val javaDocumentationBuilder = JavaDocumentationBuilder(documentationBuilder.options, + val javaDocumentationBuilder = JavaPsiDocumentationBuilder(documentationBuilder.options, documentationBuilder.refGraph, KotlinAsJavaDocumentationParser(documentationBuilder)) diff --git a/src/main.kt b/src/main.kt index d0e693e9..46b9a2dc 100644 --- a/src/main.kt +++ b/src/main.kt @@ -162,7 +162,7 @@ class DokkaGenerator(val logger: DokkaLogger, val packageDocumentationBuilder = injector.getInstance(PackageDocumentationBuilder::class.java) val documentation = buildDocumentationModule(environment, moduleName, options, includes, { isSample(it) }, - packageDocumentationBuilder, logger) + packageDocumentationBuilder, null, logger) val timeAnalyse = System.currentTimeMillis() - startAnalyse logger.info("done in ${timeAnalyse / 1000} secs") @@ -206,9 +206,10 @@ fun buildDocumentationModule(environment: AnalysisEnvironment, includes: List = listOf(), filesToDocumentFilter: (PsiFile) -> Boolean = { file -> true }, packageDocumentationBuilder: PackageDocumentationBuilder? = null, + javaDocumentationBuilder: JavaDocumentationBuilder? = null, logger: DokkaLogger): DocumentationModule { - val documentation = environment.withContext { environment, resolutionFacade, session -> - val fragmentFiles = environment.getSourceFiles().filter(filesToDocumentFilter) + val documentation = environment.withContext { coreEnvironment, resolutionFacade, session -> + val fragmentFiles = coreEnvironment.getSourceFiles().filter(filesToDocumentFilter) val analyzer = resolutionFacade.getFrontendService(LazyTopDownAnalyzerForTopLevel::class.java) analyzer.analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, fragmentFiles) @@ -231,9 +232,10 @@ fun buildDocumentationModule(environment: AnalysisEnvironment, } } - val javaFiles = environment.getJavaSourceFiles().filter(filesToDocumentFilter) - val javaDocumentationBuilder = JavaDocumentationBuilder(options, refGraph) - javaFiles.map { javaDocumentationBuilder.appendFile(it, documentationModule) } + val javaFiles = coreEnvironment.getJavaSourceFiles().filter(filesToDocumentFilter) + with(javaDocumentationBuilder ?: documentationBuilder) { + javaFiles.map { appendFile(it, documentationModule, packageDocs.packageContent) } + } refGraph.resolveReferences() diff --git a/test/data/format/javaCodeInParam.java b/test/data/format/javaCodeInParam.java index d1cdf6cd..73025fcc 100644 --- a/test/data/format/javaCodeInParam.java +++ b/test/data/format/javaCodeInParam.java @@ -1,5 +1,3 @@ -package test; - /** * @param T this is {@code some code} and other text */ diff --git a/test/data/format/javaCodeInParam.md b/test/data/format/javaCodeInParam.md index 88cb6912..b1d2d871 100644 --- a/test/data/format/javaCodeInParam.md +++ b/test/data/format/javaCodeInParam.md @@ -1,9 +1,9 @@ -[test](test/index) / [test](test/test/index) / [C](test/test/-c) +[test](test/index) / [C](test/-c/index) # C -`open class C<T>` +`protected open class C<T : Any>` @@ -13,3 +13,9 @@ `T` - this is `some code` and other text + +### Constructors + + +| [<init>](test/-c/-init-) | `C()` | + diff --git a/test/data/format/javaCodeLiteralTags.java b/test/data/format/javaCodeLiteralTags.java index cf071f07..e71ddaa7 100644 --- a/test/data/format/javaCodeLiteralTags.java +++ b/test/data/format/javaCodeLiteralTags.java @@ -1,5 +1,3 @@ -package test; - /** *

{@code AC}

*

{@literal AC}

diff --git a/test/data/format/javaCodeLiteralTags.md b/test/data/format/javaCodeLiteralTags.md index 17438bff..83c9cc33 100644 --- a/test/data/format/javaCodeLiteralTags.md +++ b/test/data/format/javaCodeLiteralTags.md @@ -1,9 +1,9 @@ -[test](test/index) / [test](test/test/index) / [C](test/test/-c) +[test](test/index) / [C](test/-c/index) # C -`open class C` +`protected open class C` `A<B>C` @@ -15,3 +15,9 @@ A<B>C + +### Constructors + + +| [<init>](test/-c/-init-) | `C()` | + diff --git a/test/data/format/javaDeprecated.html b/test/data/format/javaDeprecated.html index 9e8958a2..236c6490 100644 --- a/test/data/format/javaDeprecated.html +++ b/test/data/format/javaDeprecated.html @@ -1,13 +1,13 @@ -test / test.Foo.foo +test / Foo.foo -test / test / Foo / foo
+test / Foo / foo

foo

open fun foo(): Unit
-Deprecated: use #bar instead

+Deprecated: use #bar instead



diff --git a/test/data/format/javaDeprecated.java b/test/data/format/javaDeprecated.java index 4216f205..9694a444 100644 --- a/test/data/format/javaDeprecated.java +++ b/test/data/format/javaDeprecated.java @@ -1,5 +1,3 @@ -package test; - class Foo { /** @deprecated use {@link #bar} instead */ public void foo() {} diff --git a/test/data/format/javaLinkTag.html b/test/data/format/javaLinkTag.html index af63710d..1b6921ab 100644 --- a/test/data/format/javaLinkTag.html +++ b/test/data/format/javaLinkTag.html @@ -1,21 +1,32 @@ -test / test.Foo +test / Foo -test / test / Foo
+test / Foo

Foo

-open class Foo
-

Call #bar() to do the job.

+protected open class Foo
+

Call #bar() to do the job.



+

Constructors

+ + + + + + + +
+<init> +Foo()

Functions

+bar diff --git a/test/data/format/javaLinkTag.java b/test/data/format/javaLinkTag.java index 06452f16..84f761c6 100644 --- a/test/data/format/javaLinkTag.java +++ b/test/data/format/javaLinkTag.java @@ -1,5 +1,3 @@ -package test; - /** * Call {@link #bar()} to do the job. */ diff --git a/test/data/format/javaLinkTagWithLabel.html b/test/data/format/javaLinkTagWithLabel.html index ccf3c712..aabfbc58 100644 --- a/test/data/format/javaLinkTagWithLabel.html +++ b/test/data/format/javaLinkTagWithLabel.html @@ -1,21 +1,32 @@ -test / test.Foo +test / Foo -test / test / Foo
+test / Foo

Foo

-open class Foo
-

Call this wonderful method to do the job.

+protected open class Foo
+

Call this wonderful method to do the job.



+

Constructors

+
-bar open fun bar(): Unit
+ + + + + + +
+<init> +Foo()

Functions

+bar diff --git a/test/data/format/javaLinkTagWithLabel.java b/test/data/format/javaLinkTagWithLabel.java index 7226f1c9..1db5ad70 100644 --- a/test/data/format/javaLinkTagWithLabel.java +++ b/test/data/format/javaLinkTagWithLabel.java @@ -1,5 +1,3 @@ -package test; - /** * Call {@link #bar() this wonderful method} to do the job. */ diff --git a/test/data/format/javaSeeTag.html b/test/data/format/javaSeeTag.html index cf7b7941..a6813df3 100644 --- a/test/data/format/javaSeeTag.html +++ b/test/data/format/javaSeeTag.html @@ -1,23 +1,34 @@ -test / test.Foo +test / Foo -test / test / Foo
+test / Foo

Foo

open class Foo

See Also
-#bar
+#bar


+

Constructors

+
-bar open fun bar(): Unit
+ + + + + + +
+<init> +Foo()

Functions

+bar diff --git a/test/data/format/javaSeeTag.java b/test/data/format/javaSeeTag.java index e0b95f11..94a24606 100644 --- a/test/data/format/javaSeeTag.java +++ b/test/data/format/javaSeeTag.java @@ -1,8 +1,6 @@ -package test; - /** * @see #bar */ -class Foo { +public class Foo { public void bar() {} } \ No newline at end of file diff --git a/test/data/format/javaSpaceInAuthor.java b/test/data/format/javaSpaceInAuthor.java index b96f5884..f980ae07 100644 --- a/test/data/format/javaSpaceInAuthor.java +++ b/test/data/format/javaSpaceInAuthor.java @@ -1,5 +1,3 @@ -package test; - /** * @author Dmitry Jemerov */ diff --git a/test/data/format/javaSpaceInAuthor.md b/test/data/format/javaSpaceInAuthor.md index 425d4c9a..4d19ed01 100644 --- a/test/data/format/javaSpaceInAuthor.md +++ b/test/data/format/javaSpaceInAuthor.md @@ -1,9 +1,9 @@ -[test](test/index) / [test](test/test/index) / [C](test/test/-c) +[test](test/index) / [C](test/-c/index) # C -`open class C` +`protected open class C` @@ -11,3 +11,9 @@ Dmitry Jemerov + +### Constructors + + +| [<init>](test/-c/-init-) | `C()` | + diff --git a/test/data/format/javaSupertype.html b/test/data/format/javaSupertype.html index 2bcc2fce..4c847281 100644 --- a/test/data/format/javaSupertype.html +++ b/test/data/format/javaSupertype.html @@ -1,22 +1,33 @@ -test / test.C.Bar +test / C.Bar -test / test / C / Bar
+test / C / Bar

Bar

-open class Bar : Foo
+open class Bar : Foo


+

Constructors

+
-bar open fun bar(): Unit
+ + + + + + +
+<init> +Bar()

Functions

+returnFoo +open fun returnFoo(foo: Foo): Foo
-returnFoo -open fun returnFoo(foo: Foo): Foo
diff --git a/test/data/format/javaSupertype.java b/test/data/format/javaSupertype.java index 295c319f..b3142ff6 100644 --- a/test/data/format/javaSupertype.java +++ b/test/data/format/javaSupertype.java @@ -1,5 +1,3 @@ -package test; - class C { public static class Foo { } diff --git a/test/data/format/javadocHtml.java b/test/data/format/javadocHtml.java index bcb2cf91..622116b2 100644 --- a/test/data/format/javadocHtml.java +++ b/test/data/format/javadocHtml.java @@ -1,5 +1,3 @@ -package test; - /** * Bold * Strong diff --git a/test/data/format/javadocHtml.md b/test/data/format/javadocHtml.md index a75ebf35..db71cc3b 100644 --- a/test/data/format/javadocHtml.md +++ b/test/data/format/javadocHtml.md @@ -1,4 +1,4 @@ -[test](test/index) / [test](test/test/index) / [C](test/test/-c) +[test](test/index) / [C](test/-c/index) # C @@ -19,3 +19,9 @@ Block code + +### Constructors + + +| [<init>](test/-c/-init-) | `C()` | + diff --git a/test/data/format/javadocOrderedList.java b/test/data/format/javadocOrderedList.java index 1c7f8a3e..c32d9032 100644 --- a/test/data/format/javadocOrderedList.java +++ b/test/data/format/javadocOrderedList.java @@ -1,5 +1,3 @@ -package test; - /** *
    *
  1. Rinse
  2. diff --git a/test/data/format/javadocOrderedList.md b/test/data/format/javadocOrderedList.md index acd3e809..02a3c095 100644 --- a/test/data/format/javadocOrderedList.md +++ b/test/data/format/javadocOrderedList.md @@ -1,4 +1,4 @@ -[test](test/index) / [test](test/test/index) / [Bar](test/test/-bar) +[test](test/index) / [Bar](test/-bar/index) # Bar @@ -12,3 +12,9 @@ + +### Constructors + + +| [<init>](test/-bar/-init-) | `Bar()` | + diff --git a/test/data/java/annotatedAnnotation.java b/test/data/java/annotatedAnnotation.java index d9fae956..6296e400 100644 --- a/test/data/java/annotatedAnnotation.java +++ b/test/data/java/annotatedAnnotation.java @@ -1,6 +1,5 @@ -package test; +import java.lang.annotation.*; -@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD}) public @interface Attribute { String value() default ""; diff --git a/test/data/java/arrayType.java b/test/data/java/arrayType.java index a50e3bf6..dc42a207 100644 --- a/test/data/java/arrayType.java +++ b/test/data/java/arrayType.java @@ -1,5 +1,3 @@ -package test; - class Test { public String[] arrayToString(int[] data) { return null; diff --git a/test/data/java/constructors.java b/test/data/java/constructors.java index 3b1838dd..6f899d18 100644 --- a/test/data/java/constructors.java +++ b/test/data/java/constructors.java @@ -1,5 +1,3 @@ -package test; - class Test { public Test() {} diff --git a/test/data/java/deprecation.java b/test/data/java/deprecation.java index 07cbd901..be8decd6 100644 --- a/test/data/java/deprecation.java +++ b/test/data/java/deprecation.java @@ -1,5 +1,6 @@ -package test; - class C { - @Deprecated("This should no longer be used") void fn() {} + /** + * @deprecated This should no longer be used + */ + void fn() {} } \ No newline at end of file diff --git a/test/data/java/enumValues.java b/test/data/java/enumValues.java index 0f622946..d8dda934 100644 --- a/test/data/java/enumValues.java +++ b/test/data/java/enumValues.java @@ -1,5 +1,3 @@ -package test; - enum E { Foo } \ No newline at end of file diff --git a/test/data/java/field.java b/test/data/java/field.java index 47dac0a4..d9ae75f9 100644 --- a/test/data/java/field.java +++ b/test/data/java/field.java @@ -1,5 +1,3 @@ -package test; - class Test { public int i; public static final String s; diff --git a/test/data/java/inheritorLinks.java b/test/data/java/inheritorLinks.java index 96caeb1d..2378f5c6 100644 --- a/test/data/java/inheritorLinks.java +++ b/test/data/java/inheritorLinks.java @@ -1,5 +1,3 @@ -package test; - class C { public static class Foo { } diff --git a/test/data/java/innerClass.java b/test/data/java/innerClass.java index dd08e100..0f1be948 100644 --- a/test/data/java/innerClass.java +++ b/test/data/java/innerClass.java @@ -1,5 +1,3 @@ -package test; - class Test { public class D { } diff --git a/test/data/java/javaLangObject.java b/test/data/java/javaLangObject.java index 8f16629c..be3dd570 100644 --- a/test/data/java/javaLangObject.java +++ b/test/data/java/javaLangObject.java @@ -1,5 +1,3 @@ -package test; - class Test { public Object fn() { return null; } } diff --git a/test/data/java/member.java b/test/data/java/member.java index a417ff7e..d4f4b8d5 100644 --- a/test/data/java/member.java +++ b/test/data/java/member.java @@ -1,5 +1,3 @@ -package test - class Test { /** * Summary for Function diff --git a/test/data/java/memberWithModifiers.java b/test/data/java/memberWithModifiers.java index 7c236224..ea645c21 100644 --- a/test/data/java/memberWithModifiers.java +++ b/test/data/java/memberWithModifiers.java @@ -1,6 +1,4 @@ -package test - -abstract class Test { +public abstract class Test { /** * Summary for Function * @param name is String parameter diff --git a/test/data/java/staticMethod.java b/test/data/java/staticMethod.java index 2bbbbf1b..a2ecd7f1 100644 --- a/test/data/java/staticMethod.java +++ b/test/data/java/staticMethod.java @@ -1,5 +1,3 @@ -package test; - class C { public static void foo() { } diff --git a/test/data/java/superClass.java b/test/data/java/superClass.java index 99535813..31b5fa96 100644 --- a/test/data/java/superClass.java +++ b/test/data/java/superClass.java @@ -1,4 +1,2 @@ -package test; - public class Foo extends Exception implements Cloneable { } diff --git a/test/data/java/typeParameter.java b/test/data/java/typeParameter.java index 98a6aee9..5a24b30a 100644 --- a/test/data/java/typeParameter.java +++ b/test/data/java/typeParameter.java @@ -1,5 +1,3 @@ -package test - class Foo> { public E foo(); } diff --git a/test/data/java/varargs.java b/test/data/java/varargs.java index a6af6a8d..d184564e 100644 --- a/test/data/java/varargs.java +++ b/test/data/java/varargs.java @@ -1,5 +1,3 @@ -package test - class Foo { public void bar(String... x); } diff --git a/test/src/TestAPI.kt b/test/src/TestAPI.kt index 4a0e4fb4..82353610 100644 --- a/test/src/TestAPI.kt +++ b/test/src/TestAPI.kt @@ -2,6 +2,7 @@ package org.jetbrains.dokka.tests import com.intellij.openapi.application.PathManager import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.io.FileUtil import org.jetbrains.dokka.* import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity @@ -66,11 +67,34 @@ public fun verifyModel(source: String, verifier = verifier) } -public fun verifyPackageMember(kotlinSource: String, +public fun verifyPackageMember(source: String, withJdk: Boolean = false, withKotlinRuntime: Boolean = false, verifier: (DocumentationNode) -> Unit) { - verifyModel(kotlinSource, withJdk = withJdk, withKotlinRuntime = withKotlinRuntime) { model -> + verifyModel(source, withJdk = withJdk, withKotlinRuntime = withKotlinRuntime) { model -> + val pkg = model.members.single() + verifier(pkg.members.single()) + } +} + +public fun verifyJavaModel(source: String, + withKotlinRuntime: Boolean = false, + verifier: (DocumentationModule) -> Unit) { + val tempDir = FileUtil.createTempDirectory("dokka", "") + try { + val sourceFile = File(source) + FileUtil.copy(sourceFile, File(tempDir, sourceFile.name)) + verifyModel(JavaSourceRoot(tempDir), withJdk = true, withKotlinRuntime = withKotlinRuntime, verifier = verifier) + } + finally { + FileUtil.delete(tempDir) + } +} + +public fun verifyJavaPackageMember(source: String, + withKotlinRuntime: Boolean = false, + verifier: (DocumentationNode) -> Unit) { + verifyJavaModel(source, withKotlinRuntime) { model -> val pkg = model.members.single() verifier(pkg.members.single()) } @@ -82,15 +106,22 @@ public fun verifyOutput(roots: Array, withKotlinRuntime: Boolean = false, outputGenerator: (DocumentationModule, StringBuilder) -> Unit) { verifyModel(*roots, withJdk = withJdk, withKotlinRuntime = withKotlinRuntime) { - val output = StringBuilder() - outputGenerator(it, output) - val ext = outputExtension.removePrefix(".") - val path = roots.first().path - val expectedOutput = File(path.replaceAfterLast(".", ext, path + "." + ext)).readText() - assertEqualsIgnoringSeparators(expectedOutput, output.toString()) + verifyModelOutput(it, outputExtension, outputGenerator, roots.first().path) } } +private fun verifyModelOutput(it: DocumentationModule, + outputExtension: String, + outputGenerator: (DocumentationModule, StringBuilder) -> Unit, + sourcePath: String) { + val output = StringBuilder() + outputGenerator(it, output) + val ext = outputExtension.removePrefix(".") + val path = sourcePath + val expectedOutput = File(path.replaceAfterLast(".", ext, path + "." + ext)).readText() + assertEqualsIgnoringSeparators(expectedOutput, output.toString()) +} + public fun verifyOutput(path: String, outputExtension: String, withJdk: Boolean = false, @@ -99,6 +130,15 @@ public fun verifyOutput(path: String, verifyOutput(arrayOf(contentRootFromPath(path)), outputExtension, withJdk, withKotlinRuntime, outputGenerator) } +public fun verifyJavaOutput(path: String, + outputExtension: String, + withKotlinRuntime: Boolean = false, + outputGenerator: (DocumentationModule, StringBuilder) -> Unit) { + verifyJavaModel(path, withKotlinRuntime) { model -> + verifyModelOutput(model, outputExtension, outputGenerator, path) + } +} + public fun assertEqualsIgnoringSeparators(expectedOutput: String, output: String) { Assert.assertEquals(expectedOutput.replace("\r\n", "\n"), output.replace("\r\n", "\n")) } diff --git a/test/src/format/HtmlFormatTest.kt b/test/src/format/HtmlFormatTest.kt index 66fd0fee..752e4424 100644 --- a/test/src/format/HtmlFormatTest.kt +++ b/test/src/format/HtmlFormatTest.kt @@ -99,31 +99,31 @@ public class HtmlFormatTest { } @Test fun javaSupertypeLink() { - verifyOutput("test/data/format/javaSupertype.java", ".html") { model, output -> + verifyJavaOutput("test/data/format/javaSupertype.java", ".html") { model, output -> htmlService.appendNodes(tempLocation, output, model.members.single().members.single { it.name == "C"}.members.filter { it.name == "Bar"} ) } } @Test fun javaLinkTag() { - verifyOutput("test/data/format/javaLinkTag.java", ".html", withJdk = true) { model, output -> + verifyJavaOutput("test/data/format/javaLinkTag.java", ".html") { model, output -> htmlService.appendNodes(tempLocation, output, model.members.single().members) } } @Test fun javaLinkTagWithLabel() { - verifyOutput("test/data/format/javaLinkTagWithLabel.java", ".html", withJdk = true) { model, output -> + verifyJavaOutput("test/data/format/javaLinkTagWithLabel.java", ".html") { model, output -> htmlService.appendNodes(tempLocation, output, model.members.single().members) } } @Test fun javaSeeTag() { - verifyOutput("test/data/format/javaSeeTag.java", ".html", withJdk = true) { model, output -> + verifyJavaOutput("test/data/format/javaSeeTag.java", ".html") { model, output -> htmlService.appendNodes(tempLocation, output, model.members.single().members) } } @Test fun javaDeprecated() { - verifyOutput("test/data/format/javaDeprecated.java", ".html") { model, output -> + verifyJavaOutput("test/data/format/javaDeprecated.java", ".html") { model, output -> htmlService.appendNodes(tempLocation, output, model.members.single().members.single { it.name == "Foo" }.members.filter { it.name == "foo" }) } } diff --git a/test/src/format/MarkdownFormatTest.kt b/test/src/format/MarkdownFormatTest.kt index bb61fa1c..1a09ae60 100644 --- a/test/src/format/MarkdownFormatTest.kt +++ b/test/src/format/MarkdownFormatTest.kt @@ -121,25 +121,25 @@ public class MarkdownFormatTest { } @Test fun javadocHtml() { - verifyOutput("test/data/format/javadocHtml.java", ".md", withJdk = true) { model, output -> + verifyJavaOutput("test/data/format/javadocHtml.java", ".md") { model, output -> markdownService.appendNodes(tempLocation, output, model.members.single().members) } } @Test fun javaCodeLiteralTags() { - verifyOutput("test/data/format/javaCodeLiteralTags.java", ".md", withJdk = true) { model, output -> + verifyJavaOutput("test/data/format/javaCodeLiteralTags.java", ".md") { model, output -> markdownService.appendNodes(tempLocation, output, model.members.single().members) } } @Test fun javaCodeInParam() { - verifyOutput("test/data/format/javaCodeInParam.java", ".md", withJdk = true) { model, output -> + verifyJavaOutput("test/data/format/javaCodeInParam.java", ".md") { model, output -> markdownService.appendNodes(tempLocation, output, model.members.single().members) } } @Test fun javaSpaceInAuthor() { - verifyOutput("test/data/format/javaSpaceInAuthor.java", ".md", withJdk = true) { model, output -> + verifyJavaOutput("test/data/format/javaSpaceInAuthor.java", ".md") { model, output -> markdownService.appendNodes(tempLocation, output, model.members.single().members) } } @@ -157,7 +157,7 @@ public class MarkdownFormatTest { } @Test fun javadocOrderedList() { - verifyOutput("test/data/format/javadocOrderedList.java", ".md", withJdk = true) { model, output -> + verifyJavaOutput("test/data/format/javadocOrderedList.java", ".md") { model, output -> markdownService.appendNodes(tempLocation, output, model.members.single().members.filter { it.name == "Bar" }) } } diff --git a/test/src/model/JavaTest.kt b/test/src/model/JavaTest.kt index 5ec29b25..0633a5cc 100644 --- a/test/src/model/JavaTest.kt +++ b/test/src/model/JavaTest.kt @@ -9,12 +9,11 @@ import kotlin.test.assertTrue public class JavaTest { @Test fun function() { - verifyPackageMember("test/data/java/member.java") { cls -> + verifyJavaPackageMember("test/data/java/member.java") { cls -> assertEquals("Test", cls.name) assertEquals(DocumentationNode.Kind.Class, cls.kind) - with(cls.members.single()) { + with(cls.members(DocumentationNode.Kind.Function).single()) { assertEquals("fn", name) - assertEquals(DocumentationNode.Kind.Function, kind) assertEquals("Summary for Function", content.summary.toTestString().trimEnd()) assertEquals(3, content.sections.size) with(content.sections[0]) { @@ -47,8 +46,9 @@ public class JavaTest { } @Test fun memberWithModifiers() { - verifyPackageMember("test/data/java/memberWithModifiers.java") { cls -> - assertEquals("abstract", cls.details[0].name) + verifyJavaPackageMember("test/data/java/memberWithModifiers.java") { cls -> + val modifiers = cls.details(DocumentationNode.Kind.Modifier).map { it.name } + assertTrue("abstract" in modifiers) with(cls.members.single { it.name == "fn" }) { assertEquals("protected", details[0].name) } @@ -59,7 +59,7 @@ public class JavaTest { } @Test fun superClass() { - verifyPackageMember("test/data/java/superClass.java") { cls -> + verifyJavaPackageMember("test/data/java/superClass.java") { cls -> val superTypes = cls.details(DocumentationNode.Kind.Supertype) assertEquals(2, superTypes.size) assertEquals("Exception", superTypes[0].name) @@ -68,22 +68,21 @@ public class JavaTest { } @Test fun arrayType() { - verifyPackageMember("test/data/java/arrayType.java") { cls -> - with(cls.members.single()) { + verifyJavaPackageMember("test/data/java/arrayType.java") { cls -> + with(cls.members(DocumentationNode.Kind.Function).single()) { val type = detail(DocumentationNode.Kind.Type) assertEquals("Array", type.name) assertEquals("String", type.detail(DocumentationNode.Kind.Type).name) with(details(DocumentationNode.Kind.Parameter).single()) { val parameterType = detail(DocumentationNode.Kind.Type) - assertEquals("Array", parameterType.name) - assertEquals("Int", parameterType.detail(DocumentationNode.Kind.Type).name) + assertEquals("IntArray", parameterType.name) } } } } @Test fun typeParameter() { - verifyPackageMember("test/data/java/typeParameter.java") { cls -> + verifyJavaPackageMember("test/data/java/typeParameter.java") { cls -> val typeParameters = cls.details(DocumentationNode.Kind.TypeParameter) with(typeParameters.single()) { assertEquals("T", name) @@ -92,7 +91,7 @@ public class JavaTest { assertEquals("T", detail(DocumentationNode.Kind.Type).name) } } - with(cls.members.single()) { + with(cls.members(DocumentationNode.Kind.Function).single()) { val methodTypeParameters = details(DocumentationNode.Kind.TypeParameter) with(methodTypeParameters.single()) { assertEquals("E", name) @@ -102,7 +101,7 @@ public class JavaTest { } @Test fun constructors() { - verifyPackageMember("test/data/java/constructors.java") { cls -> + verifyJavaPackageMember("test/data/java/constructors.java") { cls -> val constructors = cls.members(DocumentationNode.Kind.Constructor) assertEquals(2, constructors.size) with(constructors[0]) { @@ -112,17 +111,17 @@ public class JavaTest { } @Test fun innerClass() { - verifyPackageMember("test/data/java/innerClass.java") { cls -> + verifyJavaPackageMember("test/data/java/innerClass.java") { cls -> val innerClass = cls.members(DocumentationNode.Kind.Class).single() assertEquals("D", innerClass.name) } } @Test fun varargs() { - verifyPackageMember("test/data/java/varargs.java") { cls -> + verifyJavaPackageMember("test/data/java/varargs.java") { cls -> val fn = cls.members(DocumentationNode.Kind.Function).single() val param = fn.detail(DocumentationNode.Kind.Parameter) - assertEquals("vararg", param.annotations.first().name) + assertEquals("vararg", param.details(DocumentationNode.Kind.Modifier).first().name) val psiType = param.detail(DocumentationNode.Kind.Type) assertEquals("String", psiType.name) assertTrue(psiType.details(DocumentationNode.Kind.Type).isEmpty()) @@ -130,26 +129,28 @@ public class JavaTest { } @Test fun fields() { - verifyPackageMember("test/data/java/field.java") { cls -> + verifyJavaPackageMember("test/data/java/field.java") { cls -> val i = cls.members(DocumentationNode.Kind.Property).single { it.name == "i" } assertEquals("Int", i.detail(DocumentationNode.Kind.Type).name) assertTrue("var" in i.details(DocumentationNode.Kind.Modifier).map { it.name }) - val s = cls.members(DocumentationNode.Kind.CompanionObjectProperty).single { it.name == "s" } + + val s = cls.members(DocumentationNode.Kind.Property).single { it.name == "s" } assertEquals("String", s.detail(DocumentationNode.Kind.Type).name) assertFalse("var" in s.details(DocumentationNode.Kind.Modifier).map { it.name }) + assertTrue("static" in s.details(DocumentationNode.Kind.Modifier).map { it.name }) } } @Test fun staticMethod() { - verifyPackageMember("test/data/java/staticMethod.java") { cls -> - val m = cls.members(DocumentationNode.Kind.CompanionObjectFunction).single { it.name == "foo" } - assertFalse("static" in m.details(DocumentationNode.Kind.Modifier).map { it.name }) + verifyJavaPackageMember("test/data/java/staticMethod.java") { cls -> + val m = cls.members(DocumentationNode.Kind.Function).single { it.name == "foo" } + assertTrue("static" in m.details(DocumentationNode.Kind.Modifier).map { it.name }) } } @Test fun annotatedAnnotation() { - verifyPackageMember("test/data/java/annotatedAnnotation.java") { cls -> - assertEquals(2, cls.annotations.size) + verifyJavaPackageMember("test/data/java/annotatedAnnotation.java") { cls -> + assertEquals(1, cls.annotations.size) with(cls.annotations[0]) { assertEquals(1, details.count()) with(details[0]) { @@ -157,7 +158,7 @@ public class JavaTest { assertEquals(1, details.count()) with(details[0]) { assertEquals(DocumentationNode.Kind.Value, kind) - assertEquals("RetentionPolicy.RUNTIME", name) + assertEquals("[AnnotationTarget.FIELD, AnnotationTarget.CLASS, AnnotationTarget.FILE, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER]", name) } } } @@ -165,31 +166,21 @@ public class JavaTest { } @Test fun deprecation() { - verifyPackageMember("test/data/java/deprecation.java", withJdk = true) { cls -> + verifyJavaPackageMember("test/data/java/deprecation.java") { cls -> val fn = cls.members(DocumentationNode.Kind.Function).single() - with(fn.deprecation!!) { - assertEquals(1, details.count()) - with(details[0]) { - assertEquals(DocumentationNode.Kind.Parameter, kind) - assertEquals(1, details.count()) - with(details[0]) { - assertEquals(DocumentationNode.Kind.Value, kind) - assertEquals("This should no longer be used", name) - } - } - } + assertEquals("This should no longer be used", fn.deprecation!!.content.toTestString()) } } @Test fun javaLangObject() { - verifyPackageMember("test/data/java/javaLangObject.java", withJdk = true) { cls -> + verifyJavaPackageMember("test/data/java/javaLangObject.java") { cls -> val fn = cls.members(DocumentationNode.Kind.Function).single() assertEquals("Any", fn.detail(DocumentationNode.Kind.Type).name) } } @Test fun enumValues() { - verifyPackageMember("test/data/java/enumValues.java", withJdk = true) { cls -> + verifyJavaPackageMember("test/data/java/enumValues.java") { cls -> val superTypes = cls.details(DocumentationNode.Kind.Supertype) assertEquals(0, superTypes.size) assertEquals(1, cls.members(DocumentationNode.Kind.EnumItem).size) @@ -197,7 +188,7 @@ public class JavaTest { } @Test fun inheritorLinks() { - verifyPackageMember("test/data/java/inheritorLinks.java") { cls -> + verifyJavaPackageMember("test/data/java/inheritorLinks.java") { cls -> val fooClass = cls.members.single { it.name == "Foo" } val inheritors = fooClass.references(DocumentationReference.Kind.Inheritor) assertEquals(1, inheritors.size) -- cgit