<lambda>null1 package org.jetbrains.dokka.Formats
2
3 import com.google.common.base.Throwables
4 import kotlinx.html.*
5 import kotlinx.html.Entities.nbsp
6 import kotlinx.html.stream.appendHTML
7 import org.jetbrains.dokka.*
8 import org.jetbrains.dokka.LanguageService.RenderMode.FULL
9 import org.jetbrains.dokka.LanguageService.RenderMode.SUMMARY
10 import org.jetbrains.dokka.NodeKind.Companion.classLike
11 import java.net.URI
12 import javax.inject.Inject
13
14
15 open class JavaLayoutHtmlFormatOutputBuilder(
16 val output: Appendable,
17 val languageService: LanguageService,
18 val uriProvider: JavaLayoutHtmlUriProvider,
19 val templateService: JavaLayoutHtmlTemplateService,
20 val logger: DokkaLogger,
21 val uri: URI
22 ) {
23
24 val htmlConsumer = output.appendHTML()
25
26
27 private fun FlowContent.hN(
28 level: Int,
29 classes: String? = null,
30 block: CommonAttributeGroupFacadeFlowHeadingPhrasingContent.() -> Unit
31 ) {
32 when (level) {
33 1 -> h1(classes, block)
34 2 -> h2(classes, block)
35 3 -> h3(classes, block)
36 4 -> h4(classes, block)
37 5 -> h5(classes, block)
38 6 -> h6(classes, block)
39 }
40 }
41
42 protected open fun FlowContent.metaMarkup(content: List<ContentNode>, contextUri: URI = uri) =
43 contentNodesToMarkup(content, contextUri)
44
45 protected fun FlowContent.nodeContent(node: DocumentationNode, uriNode: DocumentationNode) =
46 contentNodeToMarkup(node.content, uriProvider.mainUriOrWarn(uriNode) ?: uri)
47
48 protected fun FlowContent.nodeContent(node: DocumentationNode) =
49 nodeContent(node, node)
50
51 protected fun FlowContent.contentNodesToMarkup(content: List<ContentNode>, contextUri: URI = uri): Unit =
52 content.forEach { contentNodeToMarkup(it, contextUri) }
53
54 protected fun FlowContent.contentNodeToMarkup(content: ContentNode, contextUri: URI = uri) {
55 when (content) {
56 is ContentText -> +content.text
57 is ContentSymbol -> span("symbol") { +content.text }
58 is ContentKeyword -> span("keyword") { +content.text }
59 is ContentIdentifier -> span("identifier") {
60 content.signature?.let { id = it }
61 +content.text
62 }
63
64 is ContentHeading -> hN(level = content.level) { contentNodesToMarkup(content.children, contextUri) }
65
66 is ContentEntity -> +content.text
67
68 is ContentStrong -> strong { contentNodesToMarkup(content.children, contextUri) }
69 is ContentStrikethrough -> del { contentNodesToMarkup(content.children, contextUri) }
70 is ContentEmphasis -> em { contentNodesToMarkup(content.children, contextUri) }
71
72 is ContentOrderedList -> ol { contentNodesToMarkup(content.children, contextUri) }
73 is ContentUnorderedList -> ul { contentNodesToMarkup(content.children, contextUri) }
74 is ContentListItem -> consumer.li {
75 (content.children.singleOrNull() as? ContentParagraph)
76 ?.let { paragraph -> contentNodesToMarkup(paragraph.children, contextUri) }
77 ?: contentNodesToMarkup(content.children, contextUri)
78 }
79
80 is ContentDescriptionList -> dl { contentNodesToMarkup(content.children, contextUri) }
81 is ContentDescriptionTerm -> consumer.dt {
82 (content.children.singleOrNull() as? ContentParagraph)
83 ?.let { paragraph -> this@contentNodeToMarkup.contentNodesToMarkup(paragraph.children, contextUri) }
84 ?: this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri)
85 }
86 is ContentDescriptionDefinition -> consumer.dd {
87 (content.children.singleOrNull() as? ContentParagraph)
88 ?.let { paragraph -> contentNodesToMarkup(paragraph.children, contextUri) }
89 ?: contentNodesToMarkup(content.children, contextUri)
90 }
91
92 is ContentTable -> table { contentNodesToMarkup(content.children, contextUri) }
93 is ContentTableBody -> consumer.tbody { this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri) }
94 is ContentTableRow -> consumer.tr { this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri) }
95 is ContentTableHeader -> consumer.th {
96 content.colspan?.let {
97 if (it.isNotBlank()) {
98 attributes["colspan"] = content.colspan
99 }
100 }
101 content.rowspan?.let {
102 if (it.isNotBlank()) {
103 attributes["rowspan"] = content.rowspan
104 }
105 }
106 (content.children.singleOrNull() as? ContentParagraph)
107 ?.let { paragraph -> this@contentNodeToMarkup.contentNodesToMarkup(paragraph.children, contextUri) }
108 ?: this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri)
109 }
110 is ContentTableCell -> consumer.td {
111 content.colspan?.let {
112 if (it.isNotBlank()) {
113 attributes["colspan"] = content.colspan
114 }
115 }
116 content.rowspan?.let {
117 if (it.isNotBlank()) {
118 attributes["rowspan"] = content.rowspan
119 }
120 }
121 (content.children.singleOrNull() as? ContentParagraph)
122 ?.let { paragraph -> contentNodesToMarkup(paragraph.children, contextUri) }
123 ?: contentNodesToMarkup(content.children, contextUri)
124 }
125
126 is ContentSpecialReference -> aside(classes = "note") {
127 contentNodesToMarkup(content.children, contextUri)
128 }
129
130 is ContentCode -> contentInlineCode(content)
131 is ContentBlockSampleCode -> contentBlockSampleCode(content)
132 is ContentBlockCode -> contentBlockCode(content)
133
134 ContentNonBreakingSpace -> +nbsp
135 ContentSoftLineBreak, ContentIndentedSoftLineBreak -> {
136 }
137 ContentHardLineBreak -> br
138
139 is ContentParagraph -> p(classes = content.label) { contentNodesToMarkup(content.children, contextUri) }
140
141 is NodeRenderContent -> renderedSignature(content.node, mode = content.mode)
142 is ContentNodeLink -> {
143 fun FlowContent.body() = contentNodesToMarkup(content.children, contextUri)
144
145 when (content.node?.kind) {
146 NodeKind.TypeParameter -> body()
147 else -> a(href = content.node, block = FlowContent::body)
148 }
149 }
150 is ContentBookmark -> a {
151 id = content.name
152 contentNodesToMarkup(content.children, contextUri)
153 }
154 is ContentExternalLink -> contentExternalLink(content)
155 is ContentLocalLink -> a(href = contextUri.resolve(content.href).relativeTo(uri).toString()) {
156 contentNodesToMarkup(content.children, contextUri)
157 }
158 is ContentSection -> {
159 }
160 is ScriptBlock -> script(content.type, content.src) {}
161 is ContentBlock -> contentNodesToMarkup(content.children, contextUri)
162 }
163 }
164
165 protected open fun FlowContent.contentInlineCode(content: ContentCode) {
166 code { contentNodesToMarkup(content.children) }
167 }
168
169 protected open fun FlowContent.contentBlockSampleCode(content: ContentBlockSampleCode) {
170 pre {
171 code {
172 attributes["data-language"] = content.language
173 contentNodesToMarkup(content.importsBlock.children)
174 +"\n\n"
175 contentNodesToMarkup(content.children)
176 }
177 }
178 }
179
180 protected open fun FlowContent.contentBlockCode(content: ContentBlockCode) {
181 pre {
182 code {
183 attributes["data-language"] = content.language
184 contentNodesToMarkup(content.children)
185 }
186 }
187 }
188
189 protected open fun FlowContent.contentExternalLink(content: ContentExternalLink) {
190 a(href = content.href) { contentNodesToMarkup(content.children) }
191 }
192
193 protected open fun <T> FlowContent.summaryNodeGroup(
194 nodes: Iterable<T>,
195 header: String,
196 headerAsRow: Boolean = true,
197 row: TBODY.(T) -> Unit
198 ) {
199 if (nodes.none()) return
200 if (!headerAsRow) {
201 h2 { +header }
202 }
203 table {
204 tbody {
205 if (headerAsRow) {
206 developerHeading(header)
207 }
208 nodes.forEach { node ->
209 row(node)
210 }
211 }
212 }
213 }
214
215
216 protected open fun summary(node: DocumentationNode) = node.summary
217
218 protected open fun TBODY.classLikeRow(node: DocumentationNode) = tr {
219 td { a(href = uriProvider.linkTo(node, uri)) { +node.simpleName() } }
220 td { nodeSummary(node) }
221 }
222
223 protected fun FlowContent.modifiers(node: DocumentationNode) {
224 for (modifier in node.details(NodeKind.Modifier)) {
225 renderedSignature(modifier, SUMMARY)
226 }
227 }
228
229 protected fun FlowContent.shortFunctionParametersList(func: DocumentationNode) {
230 val params = func.details(NodeKind.Parameter)
231 .map { languageService.render(it, FULL) }
232 .run {
233 drop(1).fold(listOfNotNull(firstOrNull())) { acc, node ->
234 acc + ContentText(", ") + node
235 }
236 }
237 metaMarkup(listOf(ContentText("(")) + params + listOf(ContentText(")")))
238 }
239
240
241 protected open fun TBODY.functionLikeSummaryRow(node: DocumentationNode) = tr {
242 if (node.kind != NodeKind.Constructor) {
243 td {
244 modifiers(node)
245 renderedSignature(node.detail(NodeKind.Type), SUMMARY)
246 }
247 }
248 td {
249 div {
250 code {
251 val receiver = node.detailOrNull(NodeKind.Receiver)
252 if (receiver != null) {
253 renderedSignature(receiver.detail(NodeKind.Type), SUMMARY)
254 +"."
255 }
256 a(href = node) { +node.prettyName }
257 shortFunctionParametersList(node)
258 }
259 }
260
261 nodeSummary(node)
262 }
263 }
264
265 protected open fun TBODY.propertyLikeSummaryRow(node: DocumentationNode, showSignature: Boolean = true) = tr {
266 if (showSignature) {
267 td {
268 modifiers(node)
269 renderedSignature(node.detail(NodeKind.Type), SUMMARY)
270 }
271 }
272 td {
273 div {
274 code {
275 a(href = node) { +node.name }
276 }
277 }
278
279 nodeSummary(node)
280 }
281 }
282
283 protected open fun TBODY.nestedClassSummaryRow(node: DocumentationNode) = tr {
284 td {
285 modifiers(node)
286 }
287 td {
288 div {
289 code {
290 a(href = node) { +node.name }
291 }
292 }
293
294 nodeSummary(node)
295 }
296 }
297
298 protected fun HtmlBlockTag.nodeSummary(node: DocumentationNode, uriNode: DocumentationNode) {
299 contentNodeToMarkup(summary(node), uriProvider.mainUriOrWarn(uriNode) ?: uri)
300 }
301
302 protected fun HtmlBlockTag.nodeSummary(node: DocumentationNode) {
303 nodeSummary(node, node)
304 }
305
306 protected open fun TBODY.inheritRow(
307 entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
308 summaryRow: TBODY.(DocumentationNode) -> Unit
309 ) = tr {
310 td {
311 val (from, nodes) = entry
312 +"From class "
313 a(href = from.owner!!) { +from.qualifiedName() }
314 table {
315 tbody {
316 for (node in nodes) {
317 summaryRow(node)
318 }
319 }
320 }
321 }
322 }
323
324 protected open fun TBODY.groupedRow(
325 entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
326 groupHeader: HtmlBlockTag.(DocumentationNode) -> Unit,
327 summaryRow: TBODY.(DocumentationNode) -> Unit
328 ) = tr {
329 td {
330 val (from, nodes) = entry
331 groupHeader(from)
332 table {
333 tbody {
334 for (node in nodes) {
335 summaryRow(node)
336 }
337 }
338 }
339 }
340 }
341
342 protected open fun TBODY.extensionRow(
343 entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
344 summaryRow: TBODY.(DocumentationNode) -> Unit
345 ) = groupedRow(entry, { from ->
346 +"From "
347 a(href = from) { +from.qualifiedName() }
348 }, summaryRow)
349
350
351 protected open fun TBODY.extensionByReceiverRow(
352 entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
353 summaryRow: TBODY.(DocumentationNode) -> Unit
354 ) = groupedRow(entry, { from ->
355 +"For "
356 a(href = from) { +from.name }
357 }, summaryRow)
358
359 protected open fun FlowOrInteractiveOrPhrasingContent.a(href: DocumentationNode?, classes: String? = null, block: HtmlBlockInlineTag.() -> Unit) {
360 if (href == null) {
361 return a(href = "#", classes = classes, block = block)
362 }
363
364 val hrefText = try {
365 href.name.takeIf { href.kind == NodeKind.ExternalLink }
366 ?: href.links.firstOrNull { it.kind == NodeKind.ExternalLink }?.name
367 ?: "#".takeIf { href.kind == NodeKind.ExternalClass } // When external class unresolved
368 ?: uriProvider.linkTo(href, uri)
369 } catch (e: Exception) {
370 val owners = generateSequence(href) { it.owner }.toList().reversed()
371 logger.warn("Exception while resolving link to ${owners.joinToString(separator = " ")}\n"
372 + Throwables.getStackTraceAsString(e))
373 "#"
374 }
375
376 a(href = hrefText, classes = classes, block = block)
377 }
378
379 protected open fun FlowContent.renderedSignature(
380 node: DocumentationNode,
381 mode: LanguageService.RenderMode = SUMMARY
382 ) {
383 contentNodeToMarkup(languageService.render(node, mode), uri)
384 }
385
386 protected open fun generatePackage(page: Page.PackagePage) = templateService.composePage(
387 page,
388 htmlConsumer,
389 headContent = {
390
391 },
392 bodyContent = {
393 h1 { +page.node.name }
394 nodeContent(page.node)
395 this@composePage.summaryNodeGroup(page.interfaces, "Interfaces", headerAsRow = false) { classLikeRow(it) }
396 this@composePage.summaryNodeGroup(page.classes, "Classes", headerAsRow = false) { classLikeRow(it) }
397 this@composePage.summaryNodeGroup(page.exceptions, "Exceptions", headerAsRow = false) { classLikeRow(it) }
398 this@composePage.summaryNodeGroup(page.typeAliases, "Type-aliases", headerAsRow = false) { classLikeRow(it) }
399 this@composePage.summaryNodeGroup(page.annotations, "Annotations", headerAsRow = false) { classLikeRow(it) }
400 this@composePage.summaryNodeGroup(page.enums, "Enums", headerAsRow = false) { classLikeRow(it) }
401
402 this@composePage.summaryNodeGroup(
403 page.constants,
404 "Top-level constants summary",
405 headerAsRow = false
406 ) {
407 propertyLikeSummaryRow(it)
408 }
409
410 this@composePage.summaryNodeGroup(
411 page.functions,
412 "Top-level functions summary",
413 headerAsRow = false
414 ) {
415 functionLikeSummaryRow(it)
416 }
417
418 this@composePage.summaryNodeGroup(
419 page.properties,
420 "Top-level properties summary",
421 headerAsRow = false
422 ) {
423 propertyLikeSummaryRow(it)
424 }
425
426 this@composePage.summaryNodeGroup(
427 page.extensionFunctions.entries,
428 "Extension functions summary",
429 headerAsRow = false
430 ) {
431 extensionByReceiverRow(it) {
432 functionLikeSummaryRow(it)
433 }
434 }
435
436 this@composePage.summaryNodeGroup(
437 page.extensionProperties.entries,
438 "Extension properties summary",
439 headerAsRow = false
440 ) {
441 extensionByReceiverRow(it) {
442 functionLikeSummaryRow(it)
443 }
444 }
445
446 fullMemberDocs(page.constants, "Top-level constants")
447 fullMemberDocs(page.functions, "Top-level functions")
448 fullMemberDocs(page.properties, "Top-level properties")
449 fullMemberDocs(page.extensionFunctions.values.flatten(), "Extension functions")
450 fullMemberDocs(page.extensionProperties.values.flatten(), "Extension properties")
451 }
452 )
453
454 protected fun FlowContent.qualifiedTypeReference(node: DocumentationNode) {
455 if (node.kind in classLike) {
456 a(href = node) { +node.qualifiedName() }
457 return
458 }
459
460 val targetLink = node.links.firstOrNull()
461
462 if (targetLink?.kind == NodeKind.TypeParameter) {
463 +node.name
464 return
465 }
466
467 a(href = targetLink) {
468 +node.qualifiedNameFromType()
469 }
470 val typeParameters = node.details(NodeKind.Type)
471 if (typeParameters.isNotEmpty()) {
472 +"<"
473 typeParameters.forEach {
474 if (it != typeParameters.first()) {
475 +", "
476 }
477 qualifiedTypeReference(it)
478 }
479 +">"
480 }
481 }
482
483 protected open fun FlowContent.classHierarchy(superclasses: List<DocumentationNode>) {
484 table {
485 superclasses.forEach {
486 tr {
487 if (it != superclasses.first()) {
488 td {
489 +" ↳"
490 }
491 }
492 td {
493 qualifiedTypeReference(it)
494 }
495 }
496 }
497 }
498 }
499
500 protected open fun FlowContent.subclasses(inheritors: List<DocumentationNode>, direct: Boolean) {
501 if (inheritors.isEmpty()) return
502 div {
503 table {
504 thead {
505 tr {
506 td {
507 if (direct)
508 +"Known Direct Subclasses"
509 else
510 +"Known Indirect Subclasses"
511 }
512 }
513 }
514 tbody {
515 inheritors.forEach { inheritor ->
516 tr {
517 td {
518 a(href = inheritor) { +inheritor.classNodeNameWithOuterClass() }
519 }
520 td {
521 nodeSummary(inheritor)
522 }
523 }
524 }
525 }
526 }
527 }
528 }
529
530 protected open fun FlowContent.classLikeSummaries(page: Page.ClassPage) = with(page) {
531 this@classLikeSummaries.summaryNodeGroup(
532 nestedClasses,
533 "Nested classes",
534 headerAsRow = true
535 ) {
536 nestedClassSummaryRow(it)
537 }
538
539 this@classLikeSummaries.summaryNodeGroup(enumValues, "Enum values") {
540 propertyLikeSummaryRow(it)
541 }
542
543 this@classLikeSummaries.summaryNodeGroup(constants, "Constants") { propertyLikeSummaryRow(it) }
544
545 constructors.forEach { (visibility, group) ->
546 this@classLikeSummaries.summaryNodeGroup(
547 group,
548 "${visibility.capitalize()} constructors",
549 headerAsRow = true
550 ) {
551 functionLikeSummaryRow(it)
552 }
553 }
554
555 functions.forEach { (visibility, group) ->
556 this@classLikeSummaries.summaryNodeGroup(
557 group,
558 "${visibility.capitalize()} functions",
559 headerAsRow = true
560 ) {
561 functionLikeSummaryRow(it)
562 }
563 }
564
565 this@classLikeSummaries.summaryNodeGroup(
566 companionFunctions,
567 "Companion functions",
568 headerAsRow = true
569 ) {
570 functionLikeSummaryRow(it)
571 }
572 this@classLikeSummaries.summaryNodeGroup(
573 inheritedFunctionsByReceiver.entries,
574 "Inherited functions",
575 headerAsRow = true
576 ) {
577 inheritRow(it) {
578 functionLikeSummaryRow(it)
579 }
580 }
581 this@classLikeSummaries.summaryNodeGroup(
582 extensionFunctions.entries,
583 "Extension functions",
584 headerAsRow = true
585 ) {
586 extensionRow(it) {
587 functionLikeSummaryRow(it)
588 }
589 }
590 this@classLikeSummaries.summaryNodeGroup(
591 inheritedExtensionFunctions.entries,
592 "Inherited extension functions",
593 headerAsRow = true
594 ) {
595 extensionRow(it) {
596 functionLikeSummaryRow(it)
597 }
598 }
599
600
601 this@classLikeSummaries.summaryNodeGroup(properties, "Properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
602 this@classLikeSummaries.summaryNodeGroup(
603 companionProperties,
604 "Companion properties",
605 headerAsRow = true
606 ) {
607 propertyLikeSummaryRow(it)
608 }
609
610 this@classLikeSummaries.summaryNodeGroup(
611 inheritedPropertiesByReceiver.entries,
612 "Inherited properties",
613 headerAsRow = true
614 ) {
615 inheritRow(it) {
616 propertyLikeSummaryRow(it)
617 }
618 }
619 this@classLikeSummaries.summaryNodeGroup(
620 extensionProperties.entries,
621 "Extension properties",
622 headerAsRow = true
623 ) {
624 extensionRow(it) {
625 propertyLikeSummaryRow(it)
626 }
627 }
628 this@classLikeSummaries.summaryNodeGroup(
629 inheritedExtensionProperties.entries,
630 "Inherited extension properties",
631 headerAsRow = true
632 ) {
633 extensionRow(it) {
634 propertyLikeSummaryRow(it)
635 }
636 }
637 }
638
639 protected open fun FlowContent.classLikeFullMemberDocs(page: Page.ClassPage) = with(page) {
640 fullMemberDocs(enumValues, "Enum values")
641 fullMemberDocs(constants, "Constants")
642
643 constructors.forEach { (visibility, group) ->
644 fullMemberDocs(group, "${visibility.capitalize()} constructors")
645 }
646
647 functions.forEach { (visibility, group) ->
648 fullMemberDocs(group, "${visibility.capitalize()} methods")
649 }
650
651 fullMemberDocs(properties, "Properties")
652 if (!hasMeaningfulCompanion) {
653 fullMemberDocs(companionFunctions, "Companion functions")
654 fullMemberDocs(companionProperties, "Companion properties")
655 }
656 }
657
658 protected open fun generateClassLike(page: Page.ClassPage) = templateService.composePage(
659 page,
660 htmlConsumer,
661 headContent = {
662
663 },
664 bodyContent = {
665 val node = page.node
666 with(page) {
667
668 div {
669 id = "api-info-block"
670 apiAndDeprecatedVersions(node)
671 }
672
673 if (node.artifactId.name.isNotEmpty()) {
674 div(classes = "api-level") { br { +"belongs to Maven artifact ${node.artifactId}" } }
675 }
676 h1 { +node.name }
677 pre { renderedSignature(node, FULL) }
678 classHierarchy(page.superclasses)
679
680 subclasses(page.directInheritors, true)
681 subclasses(page.indirectInheritors, false)
682
683 deprecatedClassCallOut(node)
684 nodeContent(node)
685
686 h2 { +"Summary" }
687 classLikeSummaries(page)
688 classLikeFullMemberDocs(page)
689 }
690 }
691 )
692
693 protected open fun FlowContent.classIndexSummary(node: DocumentationNode) {
694 nodeContent(node)
695 }
696
697 protected open fun generateClassIndex(page: Page.ClassIndex) = templateService.composePage(
698 page,
699 htmlConsumer,
700 headContent = {
701
702 },
703 bodyContent = {
704 h1 { +"Class Index" }
705
706
707 ul {
708 page.classesByFirstLetter.forEach { (letter) ->
709 li { a(href = "#letter_$letter") { +letter } }
710 }
711 }
712
713 page.classesByFirstLetter.forEach { (letter, classes) ->
714 h2 {
715 id = "letter_$letter"
716 +letter
717 }
718 table {
719 tbody {
720 for (node in classes) {
721 tr {
722 td {
723 a(href = uriProvider.linkTo(node, uri)) { +node.classNodeNameWithOuterClass() }
724 }
725 td {
726 if (!deprecatedIndexSummary(node)) {
727 classIndexSummary(node)
728 }
729 }
730 }
731 }
732 }
733 }
734 }
735 }
736 )
737
738 protected open fun generatePackageIndex(page: Page.PackageIndex) = templateService.composePage(
739 page,
740 htmlConsumer,
741 headContent = {
742
743 },
744 bodyContent = {
745 h1 { +"Package Index" }
746 table {
747 tbody {
748 for (node in page.packages) {
749 tr {
750 td {
751 a(href = uriProvider.linkTo(node, uri)) { +node.name }
752 }
753 td {
754 nodeContent(node)
755 }
756 }
757 }
758 }
759 }
760 }
761 )
762
763 fun generatePage(page: Page) {
764 when (page) {
765 is Page.PackageIndex -> generatePackageIndex(page)
766 is Page.ClassIndex -> generateClassIndex(page)
767 is Page.ClassPage -> generateClassLike(page)
768 is Page.PackagePage -> generatePackage(page)
769 }
770 }
771
772 protected fun FlowContent.fullMemberDocs(
773 nodes: List<DocumentationNode>,
774 header: String
775 ) {
776 if (nodes.none()) return
777 h2 {
778 +header
779 }
780 for (node in nodes) {
781 fullMemberDocs(node)
782 }
783 }
784
785 protected open fun FlowContent.seeAlsoSection(links: List<List<ContentNode>>) {
786 p { b { +"See Also" } }
787 ul {
788 links.forEach { linkParts ->
789 li { code { metaMarkup(linkParts) } }
790 }
791 }
792 }
793
794 protected open fun FlowContent.regularSection(name: String, entries: List<ContentSection>) {
795 table {
796 thead {
797 tr {
798 th {
799 colSpan = "2"
800 +name
801 }
802 }
803 }
804 tbody {
805 entries.forEach {
806 tr {
807 if (it.subjectName != null) {
808 td { +it.subjectName }
809 }
810 td {
811 metaMarkup(it.children)
812 }
813 }
814 }
815 }
816 }
817 }
818
819 protected open fun FlowContent.deprecationWarningToMarkup(
820 node: DocumentationNode,
821 prefix: Boolean = false,
822 emphasis: Boolean = true
823 ): Boolean {
824 val deprecated = formatDeprecationOrNull(node, prefix, emphasis)
825 deprecated?.let {
826 contentNodeToMarkup(deprecated, uriProvider.mainUri(node))
827 return true
828 }
829 return false
830 }
831
832 protected open fun FlowContent.deprecatedClassCallOut(node: DocumentationNode) {
833 val deprecatedLevelExists = node.deprecatedLevel.name.isNotEmpty()
834 if (deprecatedLevelExists) {
835 hr { }
836 aside(classes = "caution") {
837 strong { +node.deprecatedLevelMessage() }
838 deprecationWarningToMarkup(node, emphasis = false)
839 }
840 }
841 }
842
843 protected open fun FlowContent.deprecatedIndexSummary(node: DocumentationNode): Boolean {
844 val deprecatedLevelExists = node.deprecatedLevel.name.isNotEmpty()
845 if (deprecatedLevelExists) {
846 val em = ContentEmphasis()
847 em.append(ContentText(node.deprecatedLevelMessage()))
848 em.append(ContentText(" "))
849 for (child in node.deprecation?.content?.children ?: emptyList<ContentNode>()) {
850 em.append(child)
851 }
852 contentNodeToMarkup(em, uriProvider.mainUri(node))
853 return true
854 }
855 return false
856 }
857
858 protected open fun FlowContent.apiAndDeprecatedVersions(node: DocumentationNode) {
859 val apiLevelExists = node.apiLevel.name.isNotEmpty()
860 val sdkExtSinceExists = node.sdkExtSince.name.isNotEmpty()
861 val deprecatedLevelExists = node.deprecatedLevel.name.isNotEmpty()
862 if (apiLevelExists || sdkExtSinceExists || deprecatedLevelExists) {
863 div(classes = "api-level") {
864 if (apiLevelExists) {
865 +"Added in "
866 a(href = "https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels") {
867 +"API level ${node.apiLevel.name}"
868 }
869 }
870 if (sdkExtSinceExists) {
871 if (apiLevelExists) {
872 br
873 +"Also in "
874 } else {
875 +"Added in "
876 }
877 a(href = "https://developer.android.com/sdkExtensions") {
878 +"${node.sdkExtSince.name}"
879 }
880 }
881 if (deprecatedLevelExists) {
882 if (apiLevelExists || sdkExtSinceExists) {
883 br
884 }
885 +"Deprecated in "
886 a(href = "https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels") {
887 +"API level ${node.deprecatedLevel.name}"
888 }
889 }
890 }
891 }
892 }
893
894 protected open fun formatDeprecationOrNull(
895 node: DocumentationNode,
896 prefix: Boolean = false,
897 emphasis: Boolean = true): ContentNode? {
898 val deprecated = node.deprecation
899 deprecated?.let {
900 return ContentParagraph("caution").apply {
901 if (prefix) {
902 append(ContentStrong().apply { text(
903 if (deprecated.content.children.size == 0) "Deprecated."
904 else "Deprecated: "
905 ) })
906 }
907 val em = if (emphasis) ContentEmphasis() else ContentBlock()
908 for (child in deprecated.content.children) {
909 em.append(child)
910 }
911 append(em)
912 }
913 }
914 return null
915 }
916
917 protected open fun FlowContent.section(name: String, sectionParts: List<ContentSection>) {
918 when (name) {
919 ContentTags.SeeAlso -> seeAlsoSection(sectionParts.map { it.children.flatMap { (it as? ContentParagraph)?.children ?: listOf(it) } })
920 else -> regularSection(name, sectionParts)
921 }
922 }
923
924 protected open fun FlowContent.sections(content: Content) {
925 val sectionsByTag = content.sections.groupByTo(mutableMapOf()) { it.tag }
926
927 val seeAlso = sectionsByTag.remove(ContentTags.SeeAlso)
928
929 for ((name, entries) in sectionsByTag) {
930 section(name, entries)
931 }
932
933 seeAlso?.let { section(ContentTags.SeeAlso, it) }
934 }
935
936 protected open fun FlowContent.fullMemberDocs(node: DocumentationNode, uriNode: DocumentationNode) {
937 div {
938 id = node.signatureForAnchor(logger)
939 h3 { +node.name }
940 pre { renderedSignature(node, FULL) }
941 deprecationWarningToMarkup(node, prefix = true)
942 nodeContent(node)
943 node.constantValue()?.let { value ->
944 pre {
945 +"Value: "
946 code { +value }
947 }
948 }
949
950 sections(node.content)
951 }
952 }
953
954 protected open fun FlowContent.fullMemberDocs(node: DocumentationNode) {
955 fullMemberDocs(node, node)
956 }
957
958 sealed class Page {
959 class PackageIndex(packages: List<DocumentationNode>) : Page() {
960 init {
961 assert(packages.all { it.kind == NodeKind.Package })
962 }
963
964 val packages = packages.sortedBy { it.name }
965 }
966
967 class ClassIndex(allTypesNode: DocumentationNode) : Page() {
968 init {
969 assert(allTypesNode.kind == NodeKind.AllTypes)
970 }
971
972 // Wide-collect all nested classes
973 val classes: List<DocumentationNode> =
974 generateSequence(listOf(allTypesNode)) { nodes ->
975 nodes
976 .flatMap { it.members.filter { it.kind in NodeKind.classLike } }
977 .takeUnless { it.isEmpty() }
978 }.drop(1)
979 .flatten()
980 .sortedBy { it.classNodeNameWithOuterClass().toLowerCase() }
981 .toList()
982
983
984 // Group all classes by it's first letter and sort
985 val classesByFirstLetter =
986 classes
987 .groupBy {
988 it.classNodeNameWithOuterClass().first().toString()
989 }
990 .entries
991 .sortedBy { (letter) ->
992 val x = letter.toLowerCase()
993 x
994 }
995 }
996
997 class ClassPage(val node: DocumentationNode) : Page() {
998
999 init {
1000 assert(node.kind in NodeKind.classLike)
1001 }
1002
1003 val superclasses = (sequenceOf(node) + node.superclassTypeSequence).toList().asReversed()
1004
1005 val enumValues = node.members(NodeKind.EnumItem).sortedBy { it.name }
1006
1007 val directInheritors: List<DocumentationNode>
1008 val indirectInheritors: List<DocumentationNode>
1009
1010 init {
1011 // Wide-collect all inheritors
1012 val inheritors = generateSequence(node.inheritors) { inheritors ->
1013 inheritors
1014 .flatMap { it.inheritors }
1015 .takeUnless { it.isEmpty() }
1016 }
1017 directInheritors = inheritors.first().sortedBy { it.classNodeNameWithOuterClass() }
1018 indirectInheritors = inheritors.drop(1).flatten().toList().sortedBy { it.classNodeNameWithOuterClass() }
1019 }
1020
1021 val isCompanion = node.details(NodeKind.Modifier).any { it.name == "companion" }
1022 val hasMeaningfulCompanion = !isCompanion && node.companion != null
1023
1024 private fun DocumentationNode.thisTypeExtension() =
1025 detail(NodeKind.Receiver).detail(NodeKind.Type).links.any { it == node }
1026
1027 val functionKind = if (!isCompanion) NodeKind.Function else NodeKind.CompanionObjectFunction
1028 val propertyKind = if (!isCompanion) NodeKind.Property else NodeKind.CompanionObjectProperty
1029
1030 private fun DocumentationNode.isFunction() = kind == functionKind
1031 private fun DocumentationNode.isProperty() = kind == propertyKind
1032
1033
1034 val nestedClasses = node.members.filter { it.kind in NodeKind.classLike } - enumValues
1035
1036 val attributes = node.attributes
1037
1038 val inheritedAttributes =
1039 node.superclassTypeSequence
1040 .toList()
1041 .sortedBy { it.name }
1042 .flatMap { it.typeDeclarationClass?.attributes.orEmpty() }
1043 .distinctBy { it.attributeRef!!.name }
1044 .groupBy { it.owner!! }
1045
1046 val allInheritedMembers = node.allInheritedMembers
1047 val constants = node.members.filter { it.constantValue() != null }
1048 val inheritedConstants = allInheritedMembers.filter { it.constantValue() != null }.groupBy { it.owner!! }
1049
1050
1051 fun compareVisibilities(a: String, b: String): Int {
1052 return visibilityNames.indexOf(a) - visibilityNames.indexOf(b)
1053 }
1054
1055 fun Collection<DocumentationNode>.groupByVisibility() =
1056 groupBy { it.visibility() }.toSortedMap(Comparator { a, b -> compareVisibilities(a, b) })
1057
1058
1059 val constructors = node.members(NodeKind.Constructor).groupByVisibility()
1060 val functions = node.members(functionKind).groupByVisibility()
1061 val fields = (node.members(NodeKind.Field) - constants).groupByVisibility()
1062 val properties = node.members(propertyKind) - constants
1063 val inheritedFunctionsByReceiver = allInheritedMembers.filter { it.kind == functionKind }.groupBy { it.owner!! }
1064 val inheritedPropertiesByReceiver =
1065 allInheritedMembers.filter {
1066 it.kind == propertyKind && it.constantValue() == null
1067 }.groupBy { it.owner!! }
1068
1069 val inheritedFieldsByReceiver =
1070 allInheritedMembers.filter {
1071 it.kind == NodeKind.Field && it.constantValue() != null
1072 }.groupBy { it.owner!! }
1073
1074 val originalExtensions = if (!isCompanion) node.extensions else node.owner!!.extensions
1075
1076 val extensionFunctions: Map<DocumentationNode, List<DocumentationNode>>
1077 val extensionProperties: Map<DocumentationNode, List<DocumentationNode>>
1078 val inheritedExtensionFunctions: Map<DocumentationNode, List<DocumentationNode>>
1079 val inheritedExtensionProperties: Map<DocumentationNode, List<DocumentationNode>>
1080
1081 init {
1082 val (extensions, inheritedExtensions) = originalExtensions.partition { it.thisTypeExtension() }
1083 extensionFunctions = extensions.filter { it.isFunction() }.sortedBy { it.name }.groupBy { it.owner!! }
1084 extensionProperties = extensions.filter { it.isProperty() }.sortedBy { it.name }.groupBy { it.owner!! }
1085 inheritedExtensionFunctions =
1086 inheritedExtensions.filter { it.isFunction() }.sortedBy { it.name }.groupBy { it.owner!! }
1087 inheritedExtensionProperties =
1088 inheritedExtensions.filter { it.isProperty() }.sortedBy { it.name }.groupBy { it.owner!! }
1089 }
1090
1091 val companionFunctions = node.members(NodeKind.CompanionObjectFunction).takeUnless { isCompanion }.orEmpty()
1092 val companionProperties =
1093 node.members(NodeKind.CompanionObjectProperty).takeUnless { isCompanion }.orEmpty() - constants
1094
1095
1096 }
1097
1098 class PackagePage(val node: DocumentationNode) : Page() {
1099
1100 init {
1101 assert(node.kind == NodeKind.Package)
1102 }
1103
1104 val interfaces = node.members(NodeKind.Interface) +
1105 node.members(NodeKind.Class).flatMap { it.members(NodeKind.Interface) }
1106 val classes = node.members(NodeKind.Class)
1107 val exceptions = node.members(NodeKind.Exception)
1108 val typeAliases = node.members(NodeKind.TypeAlias)
1109 val annotations = node.members(NodeKind.AnnotationClass)
1110 val enums = node.members(NodeKind.Enum)
1111
1112 val constants = node.members(NodeKind.Property).filter { it.constantValue() != null }
1113
1114
1115 private fun DocumentationNode.getClassExtensionReceiver() =
1116 detailOrNull(NodeKind.Receiver)?.detailOrNull(NodeKind.Type)?.takeIf {
1117 it.links.any { it.kind == NodeKind.ExternalLink || it.kind in NodeKind.classLike }
1118 }
1119
1120 private fun List<DocumentationNode>.groupedExtensions() =
1121 filter { it.getClassExtensionReceiver() != null }
1122 .groupBy {
1123 val receiverType = it.getClassExtensionReceiver()!!
1124 receiverType.links.filter { it.kind != NodeKind.ExternalLink}.firstOrNull() ?:
1125 receiverType.links(NodeKind.ExternalLink).first()
1126 }
1127
1128 private fun List<DocumentationNode>.externalExtensions(kind: NodeKind) =
1129 associateBy({ it }, { it.members(kind) })
1130 .filterNot { (_, values) -> values.isEmpty() }
1131
1132 val extensionFunctions =
1133 node.members(NodeKind.ExternalClass).externalExtensions(NodeKind.Function) +
1134 node.members(NodeKind.Function).groupedExtensions()
1135
1136 val extensionProperties =
1137 node.members(NodeKind.ExternalClass).externalExtensions(NodeKind.Property) +
1138 node.members(NodeKind.Property).groupedExtensions()
1139
1140 val functions = node.members(NodeKind.Function) - extensionFunctions.values.flatten()
1141 val properties = node.members(NodeKind.Property) - constants - extensionProperties.values.flatten()
1142
1143 }
1144 }
1145 }
1146
1147 class JavaLayoutHtmlFormatOutputBuilderFactoryImpl @Inject constructor(
1148 val uriProvider: JavaLayoutHtmlUriProvider,
1149 val languageService: LanguageService,
1150 val templateService: JavaLayoutHtmlTemplateService,
1151 val logger: DokkaLogger
1152 ) : JavaLayoutHtmlFormatOutputBuilderFactory {
createOutputBuildernull1153 override fun createOutputBuilder(output: Appendable, node: DocumentationNode): JavaLayoutHtmlFormatOutputBuilder {
1154 return createOutputBuilder(output, uriProvider.mainUri(node))
1155 }
1156
createOutputBuildernull1157 override fun createOutputBuilder(output: Appendable, uri: URI): JavaLayoutHtmlFormatOutputBuilder {
1158 return JavaLayoutHtmlFormatOutputBuilder(output, languageService, uriProvider, templateService, logger, uri)
1159 }
1160 }
1161
DocumentationNodenull1162 fun DocumentationNode.constantValue(): String? =
1163 detailOrNull(NodeKind.Value)?.name.takeIf {
1164 kind == NodeKind.Field || kind == NodeKind.Property || kind == NodeKind.CompanionObjectProperty
1165 }
1166
1167
1168 private val visibilityNames = setOf("public", "protected", "internal", "package-local", "private")
1169
visibilitynull1170 fun DocumentationNode.visibility(): String =
1171 details(NodeKind.Modifier).firstOrNull { it.name in visibilityNames }?.name ?: ""
1172