diff options
author | Tiem Song <tiem@google.com> | 2019-07-03 14:16:39 -0700 |
---|---|---|
committer | Tiem Song <tiem@google.com> | 2019-07-10 10:52:51 -0700 |
commit | e1dd51247a902084dc8cc3e0601b4e78cc1d8fc1 (patch) | |
tree | 0b413d06d9c733301c537e6ee7a8dc8819457369 /core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt | |
parent | f1827c37b50434598d91e7df414f985343e2e740 (diff) | |
download | dokka-e1dd51247a902084dc8cc3e0601b4e78cc1d8fc1.tar.gz |
Adding Dokka source to AOSP
This is taken from commit 4ff3d4153ca344398bffcdaaa28f1a1f6e76f6ad from
https://github.com/google/dokka, which is Google's customized fork of
Dokka (https://github.com/Kotlin/dokka).
Bug: 135767980
Test: ./gradlew :core:cleanTest :core:test
Test: ./gradlew :runners:gradle-integration-tests:clean :runners:gradle-integration-tests:test
Change-Id: I332d0b522706e353c3837f6308aac1a8340d71d3
Diffstat (limited to 'core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt')
-rw-r--r-- | core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt | 1155 |
1 files changed, 1155 insertions, 0 deletions
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt new file mode 100644 index 000000000..9ffa7a250 --- /dev/null +++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt @@ -0,0 +1,1155 @@ +package org.jetbrains.dokka.Formats + +import com.google.common.base.Throwables +import kotlinx.html.* +import kotlinx.html.Entities.nbsp +import kotlinx.html.stream.appendHTML +import org.jetbrains.dokka.* +import org.jetbrains.dokka.LanguageService.RenderMode.FULL +import org.jetbrains.dokka.LanguageService.RenderMode.SUMMARY +import org.jetbrains.dokka.NodeKind.Companion.classLike +import java.net.URI +import javax.inject.Inject + + +open class JavaLayoutHtmlFormatOutputBuilder( + val output: Appendable, + val languageService: LanguageService, + val uriProvider: JavaLayoutHtmlUriProvider, + val templateService: JavaLayoutHtmlTemplateService, + val logger: DokkaLogger, + val uri: URI +) { + + val htmlConsumer = output.appendHTML() + + + private fun FlowContent.hN( + level: Int, + classes: String? = null, + block: CommonAttributeGroupFacadeFlowHeadingPhrasingContent.() -> Unit + ) { + when (level) { + 1 -> h1(classes, block) + 2 -> h2(classes, block) + 3 -> h3(classes, block) + 4 -> h4(classes, block) + 5 -> h5(classes, block) + 6 -> h6(classes, block) + } + } + + protected open fun FlowContent.metaMarkup(content: List<ContentNode>, contextUri: URI = uri) = + contentNodesToMarkup(content, contextUri) + + protected fun FlowContent.nodeContent(node: DocumentationNode, uriNode: DocumentationNode) = + contentNodeToMarkup(node.content, uriProvider.mainUriOrWarn(uriNode) ?: uri) + + protected fun FlowContent.nodeContent(node: DocumentationNode) = + nodeContent(node, node) + + protected fun FlowContent.contentNodesToMarkup(content: List<ContentNode>, contextUri: URI = uri): Unit = + content.forEach { contentNodeToMarkup(it, contextUri) } + + private fun FlowContent.contentNodeToMarkup(content: ContentNode, contextUri: URI) { + when (content) { + is ContentText -> +content.text + is ContentSymbol -> span("symbol") { +content.text } + is ContentKeyword -> span("keyword") { +content.text } + is ContentIdentifier -> span("identifier") { + content.signature?.let { id = it } + +content.text + } + + is ContentHeading -> hN(level = content.level) { contentNodesToMarkup(content.children, contextUri) } + + is ContentEntity -> +content.text + + is ContentStrong -> strong { contentNodesToMarkup(content.children, contextUri) } + is ContentStrikethrough -> del { contentNodesToMarkup(content.children, contextUri) } + is ContentEmphasis -> em { contentNodesToMarkup(content.children, contextUri) } + + is ContentOrderedList -> ol { contentNodesToMarkup(content.children, contextUri) } + is ContentUnorderedList -> ul { contentNodesToMarkup(content.children, contextUri) } + is ContentListItem -> consumer.li { + (content.children.singleOrNull() as? ContentParagraph) + ?.let { paragraph -> contentNodesToMarkup(paragraph.children, contextUri) } + ?: contentNodesToMarkup(content.children, contextUri) + } + + is ContentDescriptionList -> dl { contentNodesToMarkup(content.children, contextUri) } + is ContentDescriptionTerm -> consumer.dt { + (content.children.singleOrNull() as? ContentParagraph) + ?.let { paragraph -> this@contentNodeToMarkup.contentNodesToMarkup(paragraph.children, contextUri) } + ?: this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri) + } + is ContentDescriptionDefinition -> consumer.dd { + (content.children.singleOrNull() as? ContentParagraph) + ?.let { paragraph -> contentNodesToMarkup(paragraph.children, contextUri) } + ?: contentNodesToMarkup(content.children, contextUri) + } + + is ContentTable -> table { contentNodesToMarkup(content.children, contextUri) } + is ContentTableBody -> consumer.tbody { this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri) } + is ContentTableRow -> consumer.tr { this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri) } + is ContentTableHeader -> consumer.th { + content.colspan?.let { + if (it.isNotBlank()) { + attributes["colspan"] = content.colspan + } + } + content.rowspan?.let { + if (it.isNotBlank()) { + attributes["rowspan"] = content.rowspan + } + } + (content.children.singleOrNull() as? ContentParagraph) + ?.let { paragraph -> this@contentNodeToMarkup.contentNodesToMarkup(paragraph.children, contextUri) } + ?: this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri) + } + is ContentTableCell -> consumer.td { + content.colspan?.let { + if (it.isNotBlank()) { + attributes["colspan"] = content.colspan + } + } + content.rowspan?.let { + if (it.isNotBlank()) { + attributes["rowspan"] = content.rowspan + } + } + (content.children.singleOrNull() as? ContentParagraph) + ?.let { paragraph -> contentNodesToMarkup(paragraph.children, contextUri) } + ?: contentNodesToMarkup(content.children, contextUri) + } + + is ContentSpecialReference -> aside(classes = "note") { + contentNodesToMarkup(content.children, contextUri) + } + + is ContentCode -> contentInlineCode(content) + is ContentBlockSampleCode -> contentBlockSampleCode(content) + is ContentBlockCode -> contentBlockCode(content) + + ContentNonBreakingSpace -> +nbsp + ContentSoftLineBreak, ContentIndentedSoftLineBreak -> { + } + ContentHardLineBreak -> br + + is ContentParagraph -> p { contentNodesToMarkup(content.children, contextUri) } + + is NodeRenderContent -> renderedSignature(content.node, mode = content.mode) + is ContentNodeLink -> { + fun FlowContent.body() = contentNodesToMarkup(content.children, contextUri) + + when (content.node?.kind) { + NodeKind.TypeParameter -> body() + else -> a(href = content.node, block = FlowContent::body) + } + } + is ContentBookmark -> a { + id = content.name + contentNodesToMarkup(content.children, contextUri) + } + is ContentExternalLink -> contentExternalLink(content) + is ContentLocalLink -> a(href = contextUri.resolve(content.href).relativeTo(uri).toString()) { + contentNodesToMarkup(content.children, contextUri) + } + is ContentSection -> { + } + is ContentBlock -> contentNodesToMarkup(content.children, contextUri) + } + } + + protected open fun FlowContent.contentInlineCode(content: ContentCode) { + code { contentNodesToMarkup(content.children) } + } + + protected open fun FlowContent.contentBlockSampleCode(content: ContentBlockSampleCode) { + pre { + code { + attributes["data-language"] = content.language + contentNodesToMarkup(content.importsBlock.children) + +"\n\n" + contentNodesToMarkup(content.children) + } + } + } + + protected open fun FlowContent.contentBlockCode(content: ContentBlockCode) { + pre { + code { + attributes["data-language"] = content.language + contentNodesToMarkup(content.children) + } + } + } + + protected open fun FlowContent.contentExternalLink(content: ContentExternalLink) { + a(href = content.href) { contentNodesToMarkup(content.children) } + } + + protected open fun <T> FlowContent.summaryNodeGroup( + nodes: Iterable<T>, + header: String, + headerAsRow: Boolean = true, + row: TBODY.(T) -> Unit + ) { + if (nodes.none()) return + if (!headerAsRow) { + h2 { +header } + } + table { + tbody { + if (headerAsRow) { + developerHeading(header) + } + nodes.forEach { node -> + row(node) + } + } + } + } + + + protected open fun summary(node: DocumentationNode) = node.summary + + protected open fun TBODY.classLikeRow(node: DocumentationNode) = tr { + td { a(href = uriProvider.linkTo(node, uri)) { +node.simpleName() } } + td { nodeSummary(node) } + } + + protected fun FlowContent.modifiers(node: DocumentationNode) { + for (modifier in node.details(NodeKind.Modifier)) { + renderedSignature(modifier, SUMMARY) + } + } + + protected fun FlowContent.shortFunctionParametersList(func: DocumentationNode) { + val params = func.details(NodeKind.Parameter) + .map { languageService.render(it, FULL) } + .run { + drop(1).fold(listOfNotNull(firstOrNull())) { acc, node -> + acc + ContentText(", ") + node + } + } + metaMarkup(listOf(ContentText("(")) + params + listOf(ContentText(")"))) + } + + + protected open fun TBODY.functionLikeSummaryRow(node: DocumentationNode) = tr { + if (node.kind != NodeKind.Constructor) { + td { + modifiers(node) + renderedSignature(node.detail(NodeKind.Type), SUMMARY) + } + } + td { + div { + code { + val receiver = node.detailOrNull(NodeKind.Receiver) + if (receiver != null) { + renderedSignature(receiver.detail(NodeKind.Type), SUMMARY) + +"." + } + a(href = node) { +node.name } + shortFunctionParametersList(node) + } + } + + nodeSummary(node) + } + } + + protected open fun TBODY.propertyLikeSummaryRow(node: DocumentationNode, showSignature: Boolean = true) = tr { + if (showSignature) { + td { + modifiers(node) + renderedSignature(node.detail(NodeKind.Type), SUMMARY) + } + } + td { + div { + code { + a(href = node) { +node.name } + } + } + + nodeSummary(node) + } + } + + protected open fun TBODY.nestedClassSummaryRow(node: DocumentationNode) = tr { + td { + modifiers(node) + } + td { + div { + code { + a(href = node) { +node.name } + } + } + + nodeSummary(node) + } + } + + protected fun HtmlBlockTag.nodeSummary(node: DocumentationNode, uriNode: DocumentationNode) { + contentNodeToMarkup(summary(node), uriProvider.mainUriOrWarn(uriNode) ?: uri) + } + + protected fun HtmlBlockTag.nodeSummary(node: DocumentationNode) { + nodeSummary(node, node) + } + + protected open fun TBODY.inheritRow( + entry: Map.Entry<DocumentationNode, List<DocumentationNode>>, + summaryRow: TBODY.(DocumentationNode) -> Unit + ) = tr { + td { + val (from, nodes) = entry + +"From class " + a(href = from.owner!!) { +from.qualifiedName() } + table { + tbody { + for (node in nodes) { + summaryRow(node) + } + } + } + } + } + + protected open fun TBODY.groupedRow( + entry: Map.Entry<DocumentationNode, List<DocumentationNode>>, + groupHeader: HtmlBlockTag.(DocumentationNode) -> Unit, + summaryRow: TBODY.(DocumentationNode) -> Unit + ) = tr { + td { + val (from, nodes) = entry + groupHeader(from) + table { + tbody { + for (node in nodes) { + summaryRow(node) + } + } + } + } + } + + protected open fun TBODY.extensionRow( + entry: Map.Entry<DocumentationNode, List<DocumentationNode>>, + summaryRow: TBODY.(DocumentationNode) -> Unit + ) = groupedRow(entry, { from -> + +"From " + a(href = from) { +from.qualifiedName() } + }, summaryRow) + + + protected open fun TBODY.extensionByReceiverRow( + entry: Map.Entry<DocumentationNode, List<DocumentationNode>>, + summaryRow: TBODY.(DocumentationNode) -> Unit + ) = groupedRow(entry, { from -> + +"For " + a(href = from) { +from.name } + }, summaryRow) + + protected open fun FlowOrInteractiveOrPhrasingContent.a(href: DocumentationNode?, classes: String? = null, block: HtmlBlockInlineTag.() -> Unit) { + if (href == null) { + return a(href = "#", classes = classes, block = block) + } + + val hrefText = try { + href.name.takeIf { href.kind == NodeKind.ExternalLink } + ?: href.links.firstOrNull { it.kind == NodeKind.ExternalLink }?.name + ?: "#".takeIf { href.kind == NodeKind.ExternalClass } // When external class unresolved + ?: uriProvider.linkTo(href, uri) + } catch (e: Exception) { + val owners = generateSequence(href) { it.owner }.toList().reversed() + logger.warn("Exception while resolving link to ${owners.joinToString(separator = " ")}\n" + + Throwables.getStackTraceAsString(e)) + "#" + } + + a(href = hrefText, classes = classes, block = block) + } + + protected open fun FlowContent.renderedSignature( + node: DocumentationNode, + mode: LanguageService.RenderMode = SUMMARY + ) { + contentNodeToMarkup(languageService.render(node, mode), uri) + } + + protected open fun generatePackage(page: Page.PackagePage) = templateService.composePage( + page, + htmlConsumer, + headContent = { + + }, + bodyContent = { + h1 { +page.node.name } + nodeContent(page.node) + summaryNodeGroup(page.classes, "Classes", headerAsRow = false) { classLikeRow(it) } + summaryNodeGroup(page.exceptions, "Exceptions", headerAsRow = false) { classLikeRow(it) } + summaryNodeGroup(page.typeAliases, "Type-aliases", headerAsRow = false) { classLikeRow(it) } + summaryNodeGroup(page.annotations, "Annotations", headerAsRow = false) { classLikeRow(it) } + summaryNodeGroup(page.enums, "Enums", headerAsRow = false) { classLikeRow(it) } + + summaryNodeGroup( + page.constants, + "Top-level constants summary", + headerAsRow = false + ) { + propertyLikeSummaryRow(it) + } + + summaryNodeGroup( + page.functions, + "Top-level functions summary", + headerAsRow = false + ) { + functionLikeSummaryRow(it) + } + + summaryNodeGroup( + page.properties, + "Top-level properties summary", + headerAsRow = false + ) { + propertyLikeSummaryRow(it) + } + + summaryNodeGroup( + page.extensionFunctions.entries, + "Extension functions summary", + headerAsRow = false + ) { + extensionByReceiverRow(it) { + functionLikeSummaryRow(it) + } + } + + summaryNodeGroup( + page.extensionProperties.entries, + "Extension properties summary", + headerAsRow = false + ) { + extensionByReceiverRow(it) { + functionLikeSummaryRow(it) + } + } + + fullMemberDocs(page.constants, "Top-level constants") + fullMemberDocs(page.functions, "Top-level functions") + fullMemberDocs(page.properties, "Top-level properties") + fullMemberDocs(page.extensionFunctions.values.flatten(), "Extension functions") + fullMemberDocs(page.extensionProperties.values.flatten(), "Extension properties") + } + ) + + protected fun FlowContent.qualifiedTypeReference(node: DocumentationNode) { + if (node.kind in classLike) { + a(href = node) { +node.qualifiedName() } + return + } + + val targetLink = node.links.singleOrNull() + + if (targetLink?.kind == NodeKind.TypeParameter) { + +node.name + return + } + + a(href = targetLink) { + +node.qualifiedNameFromType() + } + val typeParameters = node.details(NodeKind.Type) + if (typeParameters.isNotEmpty()) { + +"<" + typeParameters.forEach { + if (it != typeParameters.first()) { + +", " + } + qualifiedTypeReference(it) + } + +">" + } + } + + protected open fun FlowContent.classHierarchy(superclasses: List<DocumentationNode>) { + table { + superclasses.forEach { + tr { + if (it != superclasses.first()) { + td { + +" ↳" + } + } + td { + qualifiedTypeReference(it) + } + } + } + } + } + + protected open fun FlowContent.subclasses(inheritors: List<DocumentationNode>, direct: Boolean) { + if (inheritors.isEmpty()) return + div { + table { + thead { + tr { + td { + if (direct) + +"Known Direct Subclasses" + else + +"Known Indirect Subclasses" + } + } + } + tbody { + inheritors.forEach { inheritor -> + tr { + td { + a(href = inheritor) { +inheritor.classNodeNameWithOuterClass() } + } + td { + nodeSummary(inheritor) + } + } + } + } + } + } + } + + protected open fun FlowContent.classLikeSummaries(page: Page.ClassPage) = with(page) { + summaryNodeGroup( + nestedClasses, + "Nested classes", + headerAsRow = true + ) { + nestedClassSummaryRow(it) + } + + summaryNodeGroup(enumValues, "Enum values") { + propertyLikeSummaryRow(it) + } + + summaryNodeGroup(constants, "Constants") { propertyLikeSummaryRow(it) } + + constructors.forEach { (visibility, group) -> + summaryNodeGroup( + group, + "${visibility.capitalize()} constructors", + headerAsRow = true + ) { + functionLikeSummaryRow(it) + } + } + + functions.forEach { (visibility, group) -> + summaryNodeGroup( + group, + "${visibility.capitalize()} functions", + headerAsRow = true + ) { + functionLikeSummaryRow(it) + } + } + + summaryNodeGroup( + companionFunctions, + "Companion functions", + headerAsRow = true + ) { + functionLikeSummaryRow(it) + } + summaryNodeGroup( + inheritedFunctionsByReceiver.entries, + "Inherited functions", + headerAsRow = true + ) { + inheritRow(it) { + functionLikeSummaryRow(it) + } + } + summaryNodeGroup( + extensionFunctions.entries, + "Extension functions", + headerAsRow = true + ) { + extensionRow(it) { + functionLikeSummaryRow(it) + } + } + summaryNodeGroup( + inheritedExtensionFunctions.entries, + "Inherited extension functions", + headerAsRow = true + ) { + extensionRow(it) { + functionLikeSummaryRow(it) + } + } + + + summaryNodeGroup(properties, "Properties", headerAsRow = true) { propertyLikeSummaryRow(it) } + summaryNodeGroup( + companionProperties, + "Companion properties", + headerAsRow = true + ) { + propertyLikeSummaryRow(it) + } + + summaryNodeGroup( + inheritedPropertiesByReceiver.entries, + "Inherited properties", + headerAsRow = true + ) { + inheritRow(it) { + propertyLikeSummaryRow(it) + } + } + summaryNodeGroup( + extensionProperties.entries, + "Extension properties", + headerAsRow = true + ) { + extensionRow(it) { + propertyLikeSummaryRow(it) + } + } + summaryNodeGroup( + inheritedExtensionProperties.entries, + "Inherited extension properties", + headerAsRow = true + ) { + extensionRow(it) { + propertyLikeSummaryRow(it) + } + } + } + + protected open fun FlowContent.classLikeFullMemberDocs(page: Page.ClassPage) = with(page) { + fullMemberDocs(enumValues, "Enum values") + fullMemberDocs(constants, "Constants") + + constructors.forEach { (visibility, group) -> + fullMemberDocs(group, "${visibility.capitalize()} constructors") + } + + functions.forEach { (visibility, group) -> + fullMemberDocs(group, "${visibility.capitalize()} methods") + } + + fullMemberDocs(properties, "Properties") + if (!hasMeaningfulCompanion) { + fullMemberDocs(companionFunctions, "Companion functions") + fullMemberDocs(companionProperties, "Companion properties") + } + } + + protected open fun generateClassLike(page: Page.ClassPage) = templateService.composePage( + page, + htmlConsumer, + headContent = { + + }, + bodyContent = { + val node = page.node + with(page) { + + div { + id = "api-info-block" + apiAndDeprecatedVersions(node) + } + + if (node.artifactId.name.isNotEmpty()) { + div(classes = "api-level") { br { +"belongs to Maven artifact ${node.artifactId}" } } + } + h1 { +node.name } + pre { renderedSignature(node, FULL) } + classHierarchy(page.superclasses) + + subclasses(page.directInheritors, true) + subclasses(page.indirectInheritors, false) + + deprecatedClassCallOut(node) + nodeContent(node) + + h2 { +"Summary" } + classLikeSummaries(page) + classLikeFullMemberDocs(page) + } + } + ) + + protected open fun FlowContent.classIndexSummary(node: DocumentationNode) { + nodeContent(node) + } + + protected open fun generateClassIndex(page: Page.ClassIndex) = templateService.composePage( + page, + htmlConsumer, + headContent = { + + }, + bodyContent = { + h1 { +"Class Index" } + + + ul { + page.classesByFirstLetter.forEach { (letter) -> + li { a(href = "#letter_$letter") { +letter } } + } + } + + page.classesByFirstLetter.forEach { (letter, classes) -> + h2 { + id = "letter_$letter" + +letter + } + table { + tbody { + for (node in classes) { + tr { + td { + a(href = uriProvider.linkTo(node, uri)) { +node.classNodeNameWithOuterClass() } + } + td { + if (!deprecatedIndexSummary(node)) { + classIndexSummary(node) + } + } + } + } + } + } + } + } + ) + + protected open fun generatePackageIndex(page: Page.PackageIndex) = templateService.composePage( + page, + htmlConsumer, + headContent = { + + }, + bodyContent = { + h1 { +"Package Index" } + table { + tbody { + for (node in page.packages) { + tr { + td { + a(href = uriProvider.linkTo(node, uri)) { +node.name } + } + td { + nodeContent(node) + } + } + } + } + } + } + ) + + fun generatePage(page: Page) { + when (page) { + is Page.PackageIndex -> generatePackageIndex(page) + is Page.ClassIndex -> generateClassIndex(page) + is Page.ClassPage -> generateClassLike(page) + is Page.PackagePage -> generatePackage(page) + } + } + + protected fun FlowContent.fullMemberDocs( + nodes: List<DocumentationNode>, + header: String + ) { + if (nodes.none()) return + h2 { + +header + } + for (node in nodes) { + fullMemberDocs(node) + } + } + + protected open fun FlowContent.seeAlsoSection(links: List<List<ContentNode>>) { + p { b { +"See Also" } } + ul { + links.forEach { linkParts -> + li { code { metaMarkup(linkParts) } } + } + } + } + + protected open fun FlowContent.regularSection(name: String, entries: List<ContentSection>) { + table { + thead { + tr { + th { + colSpan = "2" + +name + } + } + } + tbody { + entries.forEach { + tr { + if (it.subjectName != null) { + td { +it.subjectName } + } + td { + metaMarkup(it.children) + } + } + } + } + } + } + + protected open fun FlowContent.deprecationWarningToMarkup( + node: DocumentationNode, + prefix: Boolean = false, + emphasis: Boolean = true + ): Boolean { + val deprecated = formatDeprecationOrNull(node, prefix, emphasis) + deprecated?.let { + contentNodeToMarkup(deprecated, uriProvider.mainUri(node)) + return true + } + return false + } + + protected open fun FlowContent.deprecatedClassCallOut(node: DocumentationNode) { + val deprecatedLevelExists = node.deprecatedLevel.name.isNotEmpty() + if (deprecatedLevelExists) { + hr { } + aside(classes = "caution") { + strong { +node.deprecatedLevelMessage() } + deprecationWarningToMarkup(node, emphasis = false) + } + } + } + + protected open fun FlowContent.deprecatedIndexSummary(node: DocumentationNode): Boolean { + val deprecatedLevelExists = node.deprecatedLevel.name.isNotEmpty() + if (deprecatedLevelExists) { + val em = ContentEmphasis() + em.append(ContentText(node.deprecatedLevelMessage())) + em.append(ContentText(" ")) + for (child in node.deprecation?.content?.children ?: emptyList<ContentNode>()) { + em.append(child) + } + contentNodeToMarkup(em, uriProvider.mainUri(node)) + return true + } + return false + } + + protected open fun FlowContent.apiAndDeprecatedVersions(node: DocumentationNode) { + val apiLevelExists = node.apiLevel.name.isNotEmpty() + val deprecatedLevelExists = node.deprecatedLevel.name.isNotEmpty() + if (apiLevelExists || deprecatedLevelExists) { + div(classes = "api-level") { + if (apiLevelExists) { + +"Added in " + a(href = "https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels") { + +"API level ${node.apiLevel.name}" + } + if (deprecatedLevelExists) { + br + } + } + if (deprecatedLevelExists) { + +"Deprecated in " + a(href = "https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels") { + +"API level ${node.deprecatedLevel.name}" + } + } + } + } + } + + protected open fun formatDeprecationOrNull( + node: DocumentationNode, + prefix: Boolean = false, + emphasis: Boolean = true): ContentNode? { + val deprecated = node.deprecation + deprecated?.let { + return ContentParagraph().apply { + if (prefix) { + append(ContentStrong().apply { text( + if (deprecated.content.children.size == 0) "Deprecated." + else "Deprecated: " + ) }) + } + val em = if (emphasis) ContentEmphasis() else ContentBlock() + for (child in deprecated.content.children) { + em.append(child) + } + append(em) + } + } + return null + } + + protected open fun FlowContent.section(name: String, sectionParts: List<ContentSection>) { + when (name) { + ContentTags.SeeAlso -> seeAlsoSection(sectionParts.map { it.children.flatMap { (it as? ContentParagraph)?.children ?: listOf(it) } }) + else -> regularSection(name, sectionParts) + } + } + + protected open fun FlowContent.sections(content: Content) { + val sectionsByTag = content.sections.groupByTo(mutableMapOf()) { it.tag } + + val seeAlso = sectionsByTag.remove(ContentTags.SeeAlso) + + for ((name, entries) in sectionsByTag) { + section(name, entries) + } + + seeAlso?.let { section(ContentTags.SeeAlso, it) } + } + + protected open fun FlowContent.fullMemberDocs(node: DocumentationNode, uriNode: DocumentationNode) { + div { + id = node.signatureForAnchor(logger) + h3 { +node.name } + pre { renderedSignature(node, FULL) } + deprecationWarningToMarkup(node, prefix = true) + nodeContent(node) + node.constantValue()?.let { value -> + pre { + +"Value: " + code { +value } + } + } + + sections(node.content) + } + } + + protected open fun FlowContent.fullMemberDocs(node: DocumentationNode) { + fullMemberDocs(node, node) + } + + sealed class Page { + class PackageIndex(packages: List<DocumentationNode>) : Page() { + init { + assert(packages.all { it.kind == NodeKind.Package }) + } + + val packages = packages.sortedBy { it.name } + } + + class ClassIndex(allTypesNode: DocumentationNode) : Page() { + init { + assert(allTypesNode.kind == NodeKind.AllTypes) + } + + // Wide-collect all nested classes + val classes: List<DocumentationNode> = + generateSequence(listOf(allTypesNode)) { nodes -> + nodes + .flatMap { it.members.filter { it.kind in NodeKind.classLike } } + .takeUnless { it.isEmpty() } + }.drop(1) + .flatten() + .sortedBy { it.classNodeNameWithOuterClass().toLowerCase() } + .toList() + + + // Group all classes by it's first letter and sort + val classesByFirstLetter = + classes + .groupBy { + it.classNodeNameWithOuterClass().first().toString() + } + .entries + .sortedBy { (letter) -> + val x = letter.toLowerCase() + x + } + } + + class ClassPage(val node: DocumentationNode) : Page() { + + init { + assert(node.kind in NodeKind.classLike) + } + + val superclasses = (sequenceOf(node) + node.superclassTypeSequence).toList().asReversed() + + val enumValues = node.members(NodeKind.EnumItem).sortedBy { it.name } + + val directInheritors: List<DocumentationNode> + val indirectInheritors: List<DocumentationNode> + + init { + // Wide-collect all inheritors + val inheritors = generateSequence(node.inheritors) { inheritors -> + inheritors + .flatMap { it.inheritors } + .takeUnless { it.isEmpty() } + } + directInheritors = inheritors.first().sortedBy { it.classNodeNameWithOuterClass() } + indirectInheritors = inheritors.drop(1).flatten().toList().sortedBy { it.classNodeNameWithOuterClass() } + } + + val isCompanion = node.details(NodeKind.Modifier).any { it.name == "companion" } + val hasMeaningfulCompanion = !isCompanion && node.companion != null + + private fun DocumentationNode.thisTypeExtension() = + detail(NodeKind.Receiver).detail(NodeKind.Type).links.any { it == node } + + val functionKind = if (!isCompanion) NodeKind.Function else NodeKind.CompanionObjectFunction + val propertyKind = if (!isCompanion) NodeKind.Property else NodeKind.CompanionObjectProperty + + private fun DocumentationNode.isFunction() = kind == functionKind + private fun DocumentationNode.isProperty() = kind == propertyKind + + + val nestedClasses = node.members.filter { it.kind in NodeKind.classLike } - enumValues + + val attributes = node.attributes + + val inheritedAttributes = + node.superclassTypeSequence + .toList() + .sortedBy { it.name } + .flatMap { it.typeDeclarationClass?.attributes.orEmpty() } + .distinctBy { it.attributeRef!!.name } + .groupBy { it.owner!! } + + val allInheritedMembers = node.allInheritedMembers + val constants = node.members.filter { it.constantValue() != null } + val inheritedConstants = allInheritedMembers.filter { it.constantValue() != null }.groupBy { it.owner!! } + + + fun compareVisibilities(a: String, b: String): Int { + return visibilityNames.indexOf(a) - visibilityNames.indexOf(b) + } + + fun Collection<DocumentationNode>.groupByVisibility() = + groupBy { it.visibility() }.toSortedMap(Comparator { a, b -> compareVisibilities(a, b) }) + + + val constructors = node.members(NodeKind.Constructor).groupByVisibility() + val functions = node.members(functionKind).groupByVisibility() + val fields = (node.members(NodeKind.Field) - constants).groupByVisibility() + val properties = node.members(propertyKind) - constants + val inheritedFunctionsByReceiver = allInheritedMembers.filter { it.kind == functionKind }.groupBy { it.owner!! } + val inheritedPropertiesByReceiver = + allInheritedMembers.filter { + it.kind == propertyKind && it.constantValue() == null + }.groupBy { it.owner!! } + + val inheritedFieldsByReceiver = + allInheritedMembers.filter { + it == NodeKind.Field && it.constantValue() != null + }.groupBy { it.owner!! } + + val originalExtensions = if (!isCompanion) node.extensions else node.owner!!.extensions + + val extensionFunctions: Map<DocumentationNode, List<DocumentationNode>> + val extensionProperties: Map<DocumentationNode, List<DocumentationNode>> + val inheritedExtensionFunctions: Map<DocumentationNode, List<DocumentationNode>> + val inheritedExtensionProperties: Map<DocumentationNode, List<DocumentationNode>> + + init { + val (extensions, inheritedExtensions) = originalExtensions.partition { it.thisTypeExtension() } + extensionFunctions = extensions.filter { it.isFunction() }.sortedBy { it.name }.groupBy { it.owner!! } + extensionProperties = extensions.filter { it.isProperty() }.sortedBy { it.name }.groupBy { it.owner!! } + inheritedExtensionFunctions = + inheritedExtensions.filter { it.isFunction() }.sortedBy { it.name }.groupBy { it.owner!! } + inheritedExtensionProperties = + inheritedExtensions.filter { it.isProperty() }.sortedBy { it.name }.groupBy { it.owner!! } + } + + val companionFunctions = node.members(NodeKind.CompanionObjectFunction).takeUnless { isCompanion }.orEmpty() + val companionProperties = + node.members(NodeKind.CompanionObjectProperty).takeUnless { isCompanion }.orEmpty() - constants + + + } + + class PackagePage(val node: DocumentationNode) : Page() { + + init { + assert(node.kind == NodeKind.Package) + } + + val classes = node.members(NodeKind.Class) + val exceptions = node.members(NodeKind.Exception) + val typeAliases = node.members(NodeKind.TypeAlias) + val annotations = node.members(NodeKind.AnnotationClass) + val enums = node.members(NodeKind.Enum) + + val constants = node.members(NodeKind.Property).filter { it.constantValue() != null } + + + private fun DocumentationNode.getClassExtensionReceiver() = + detailOrNull(NodeKind.Receiver)?.detailOrNull(NodeKind.Type)?.takeIf { + it.links.any { it.kind == NodeKind.ExternalLink || it.kind in NodeKind.classLike } + } + + private fun List<DocumentationNode>.groupedExtensions() = + filter { it.getClassExtensionReceiver() != null } + .groupBy { + val receiverType = it.getClassExtensionReceiver()!! + receiverType.links(NodeKind.ExternalLink).firstOrNull() + ?: receiverType.links.first { it.kind in NodeKind.classLike} + } + + private fun List<DocumentationNode>.externalExtensions(kind: NodeKind) = + associateBy({ it }, { it.members(kind) }) + .filterNot { (_, values) -> values.isEmpty() } + + val extensionFunctions = + node.members(NodeKind.ExternalClass).externalExtensions(NodeKind.Function) + + node.members(NodeKind.Function).groupedExtensions() + + val extensionProperties = + node.members(NodeKind.ExternalClass).externalExtensions(NodeKind.Property) + + node.members(NodeKind.Property).groupedExtensions() + + val functions = node.members(NodeKind.Function) - extensionFunctions.values.flatten() + val properties = node.members(NodeKind.Property) - constants - extensionProperties.values.flatten() + + } + } +} + +class JavaLayoutHtmlFormatOutputBuilderFactoryImpl @Inject constructor( + val uriProvider: JavaLayoutHtmlUriProvider, + val languageService: LanguageService, + val templateService: JavaLayoutHtmlTemplateService, + val logger: DokkaLogger +) : JavaLayoutHtmlFormatOutputBuilderFactory { + override fun createOutputBuilder(output: Appendable, node: DocumentationNode): JavaLayoutHtmlFormatOutputBuilder { + return createOutputBuilder(output, uriProvider.mainUri(node)) + } + + override fun createOutputBuilder(output: Appendable, uri: URI): JavaLayoutHtmlFormatOutputBuilder { + return JavaLayoutHtmlFormatOutputBuilder(output, languageService, uriProvider, templateService, logger, uri) + } +} + +fun DocumentationNode.constantValue(): String? = + detailOrNull(NodeKind.Value)?.name.takeIf { + kind == NodeKind.Field || kind == NodeKind.Property || kind == NodeKind.CompanionObjectProperty + } + + +private val visibilityNames = setOf("public", "protected", "internal", "package-local", "private") + +fun DocumentationNode.visibility(): String = + details(NodeKind.Modifier).firstOrNull { it.name in visibilityNames }?.name ?: ""
\ No newline at end of file |