diff options
author | Ignat Beresnev <ignat.beresnev@jetbrains.com> | 2023-02-10 15:10:05 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-10 15:10:05 +0100 |
commit | e2351ebcb15a745551fef47c707f0e4e65a707cd (patch) | |
tree | 6a4ddddce8118f3f8f3ec04abecb4e52c17cb013 /plugins/base/src/main/kotlin/translators/documentables | |
parent | f09b149b5bf6834609dec1eb70789539c5e61896 (diff) | |
download | dokka-e2351ebcb15a745551fef47c707f0e4e65a707cd.tar.gz dokka-e2351ebcb15a745551fef47c707f0e4e65a707cd.tar.bz2 dokka-e2351ebcb15a745551fef47c707f0e4e65a707cd.zip |
Sort divergent elements deterministically (#2846)
Fixes #2784
Diffstat (limited to 'plugins/base/src/main/kotlin/translators/documentables')
-rw-r--r-- | plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt | 51 |
1 files changed, 43 insertions, 8 deletions
diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index b8bf87a4..25f3c450 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -9,6 +9,7 @@ import org.jetbrains.dokka.base.transformers.documentables.ClashingDriIdentifier import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter import org.jetbrains.dokka.base.transformers.pages.tags.CustomTagContentProvider import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder.DocumentableContentBuilder +import org.jetbrains.dokka.links.Callable import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.doc.* @@ -524,30 +525,31 @@ open class DefaultPageCreator( .groupBy { it.name } // This groupBy should probably use LocationProvider // This hacks displaying actual typealias signatures along classlike ones .mapValues { if (it.value.any { it is DClasslike }) it.value.filter { it !is DTypeAlias } else it.value } - .toSortedMap(compareBy(nullsLast(String.CASE_INSENSITIVE_ORDER)) { it }) + .entries.sortedBy { it.key } .forEach { (elementName, elements) -> // This groupBy should probably use LocationProvider + val sortedElements = sortDivergentElementsDeterministically(elements) row( - dri = elements.map { it.dri }.toSet(), - sourceSets = elements.flatMap { it.sourceSets }.toSet(), + dri = sortedElements.map { it.dri }.toSet(), + sourceSets = sortedElements.flatMap { it.sourceSets }.toSet(), kind = kind, styles = emptySet(), extra = elementName?.let { name -> extra + SymbolAnchorHint(name, kind) } ?: extra ) { link( text = elementName.orEmpty(), - address = elements.first().dri, + address = sortedElements.first().dri, kind = kind, styles = setOf(ContentStyle.RowTitle), - sourceSets = elements.sourceSets.toSet(), + sourceSets = sortedElements.sourceSets.toSet(), extra = extra ) divergentGroup( ContentDivergentGroup.GroupID(name), - elements.map { it.dri }.toSet(), + sortedElements.map { it.dri }.toSet(), kind = kind, extra = extra ) { - elements.map { + sortedElements.map { instance( setOf(it.dri), it.sourceSets.toSet(), @@ -571,6 +573,26 @@ open class DefaultPageCreator( } } + /** + * Divergent elements, such as extensions for the same receiver, can have identical signatures + * if they are declared in different places. If such elements are shown on the same page together, + * they need to be rendered deterministically to have reproducible builds. + * + * For example, you can have three identical extensions, if they are declared as: + * 1) top-level in package A + * 2) top-level in package B + * 3) inside a companion object in package A/B + * + * @see divergentBlock + * + * @param elements can contain types (annotation/class/interface/object/typealias), functions and properties + * @return the original list if it has one or zero elements + */ + private fun sortDivergentElementsDeterministically(elements: List<Documentable>): List<Documentable> = + elements.takeIf { it.size > 1 } // the majority are single-element lists, but no real benchmarks done + ?.sortedWith(divergentDocumentableComparator) + ?: elements + private fun DocumentableContentBuilder.contentForCustomTagsBrief(documentable: Documentable) { val customTags = documentable.customTags if (customTags.isEmpty()) return @@ -611,6 +633,19 @@ internal val Documentable.customTags: Map<String, SourceSetDependent<CustomTagWr private val Documentable.hasSeparatePage: Boolean get() = this !is DTypeAlias +/** + * @see DefaultPageCreator.sortDivergentElementsDeterministically for usage + */ +private val divergentDocumentableComparator = + compareBy<Documentable, String?>(nullsLast()) { it.dri.packageName } + .thenBy(nullsFirst()) { it.dri.classNames } // nullsFirst for top level to be first + .thenBy( + nullsLast( + compareBy<Callable> { it.params.size } + .thenBy { it.signature() } + ) + ) { it.dri.callable } + @Suppress("UNCHECKED_CAST") private fun <T : Documentable> T.nameAfterClash(): String = ((this as? WithExtraProperties<out Documentable>)?.extra?.get(DriClashAwareName)?.value ?: name).orEmpty() @@ -624,4 +659,4 @@ internal inline fun <reified T : NamedTagWrapper> GroupedTags.withTypeNamed(): M (this[T::class] as List<Pair<DokkaSourceSet, T>>?) ?.groupByTo(linkedMapOf()) { it.second.name } ?.mapValues { (_, v) -> v.toMap() } - .orEmpty()
\ No newline at end of file + .orEmpty() |