From 1ff50691f11876101b53ba02ba6c344ab688d5df Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Tue, 25 Feb 2020 11:40:02 +0100 Subject: Crude Documentable merger --- .../documentables/DefaultDocumentableMerger.kt | 270 ++++++++++++++------- 1 file changed, 187 insertions(+), 83 deletions(-) (limited to 'plugins/base/src/main/kotlin/transformers') diff --git a/plugins/base/src/main/kotlin/transformers/documentables/DefaultDocumentableMerger.kt b/plugins/base/src/main/kotlin/transformers/documentables/DefaultDocumentableMerger.kt index 6b612733..485aca4c 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/DefaultDocumentableMerger.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/DefaultDocumentableMerger.kt @@ -3,20 +3,34 @@ package org.jetbrains.dokka.base.transformers.documentables import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.Enum import org.jetbrains.dokka.model.Function +import org.jetbrains.dokka.model.Package +import org.jetbrains.dokka.model.Annotation +import org.jetbrains.dokka.model.properties.mergeExtras +import org.jetbrains.dokka.pages.PlatformData import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.transformers.documentation.DocumentableMerger +import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult internal object DefaultDocumentableMerger : DocumentableMerger { override fun invoke(modules: Collection, context: DokkaContext): Module { - if (!modules.all { it.name == modules.first().name }) + val name = modules.map { it.name }.distinct().singleOrNull() ?: run { context.logger.error("All module names need to be the same") - return Module( - modules.first().name, - merge( - modules.flatMap { it.packages }, - Package::mergeWith - ) - ) + modules.first().name + } + + return modules.reduce { left, right -> + val list = listOf(left, right) + + Module( + name = name, + packages = merge( + list.flatMap { it.packages }, + Package::mergeWith + ), + documentation = list.platformDependentFor { documentation }, + platformData = list.flatMap { it.platformData }.distinct() + ).mergeExtras(left, right) + } } } @@ -25,91 +39,181 @@ private fun merge(elements: List, reducer: (T, T) -> T): L .reduce { _, left, right -> reducer(left, right) } .values.toList() -fun PlatformInfo.mergeWith(other: PlatformInfo?) = BasePlatformInfo( - documentationNode, - (platformData + (other?.platformData ?: emptyList())).distinct() -) +private fun Iterable.platformDependentFor( + selector: D.() -> PlatformDependent +): PlatformDependent { + val actuals = map { it.selector().map } + .flatMap { it.entries } + .associate { (k, v) -> k to v } -fun ClassPlatformInfo.mergeWith(other: ClassPlatformInfo?) = ClassPlatformInfo( - info.mergeWith(other?.info), - (inherited + (other?.inherited ?: emptyList())).distinct() -) + val expected = firstNotNullResult { it.selector().expect } -fun List.mergeClassPlatformInfo(): List = - groupingBy { it.documentationNode.children + it.inherited }.reduce { _, left, right -> - left.mergeWith(right) - }.values.toList() - -fun List.merge(): List = - groupingBy { it.documentationNode }.reduce { _, left, right -> - left.mergeWith(right) - }.values.toList() - -fun Function.mergeWith(other: Function): Function = Function( - dri, - name, - returnType, - isConstructor, - other.receiver?.let { receiver?.mergeWith(it) }, - merge(parameters + other.parameters, Parameter::mergeWith), - expected?.mergeWith(other.expected), - (actual + other.actual).merge(), - visibility = (visibility + other.visibility) -) + return PlatformDependent(actuals, expected) +} -fun Property.mergeWith(other: Property) = Property( - dri, - name, - other.receiver?.let { receiver?.mergeWith(it) }, - expected?.mergeWith(other.expected), - (actual + other.actual).merge(), - accessors = (this.accessors + other.accessors).distinct(), - visibility = (visibility + other.visibility) +private fun PlatformDependent.mergeWith(other: PlatformDependent) = PlatformDependent( + map = this + other, + expect = expect ?: other.expect ) +private fun mergeExpectActual( + elements: List, + reducer: (T, T) -> T, + platformSetter: T.(List) -> T +): List where T : Documentable, T : WithExpectActual { + + fun findExpect(actual: T, expects: List): Expect = + expects.find { it.platformData.containsAll(actual.platformData) }.let { Expect.from(it) } + + fun reduceExpectActual(entry: Map.Entry, List>): List = when (val expect = entry.key) { + Expect.NotFound -> entry.value + is Expect.Found -> entry.value.plus(expect.expect).reduce(reducer).let(::listOf) + } + + fun analyzeExpectActual(sameDriElements: List): List { + val (expect, actual) = sameDriElements.partition { it.actual.expect != null } + val mergedExpect = expect.groupBy { it.actual.expect?.path }.values.map { e -> + e.first().platformSetter(e.flatMap { it.platformData }.distinct()) + } + return actual.groupBy { findExpect(it, mergedExpect) }.flatMap { reduceExpectActual(it) } + } + + return elements.groupBy { it.dri }.values.flatMap(::analyzeExpectActual) +} + +private sealed class Expect { + object NotFound : Expect() + data class Found(val expect: T) : Expect() + + companion object { + fun from(t: T?) = t?.let(::Found) ?: NotFound + } +} + +fun Package.mergeWith(other: Package) : Package = copy( + functions = mergeExpectActual(functions + other.functions, Function::mergeWith) { copy(platformData = it) }, + properties = mergeExpectActual(properties + other.properties, Property::mergeWith) { copy(platformData = it) }, + classlikes = mergeExpectActual(classlikes + other.classlikes, Classlike::mergeWith, Classlike::setPlatformData), + packages = merge(packages + other.packages, Package::mergeWith), + documentation = documentation.mergeWith(other.documentation), + platformData = (platformData + other.platformData).distinct() +).mergeExtras(this, other) + +fun Function.mergeWith(other: Function) : Function = copy( + parameters = merge(this.parameters + other.parameters, Parameter::mergeWith), + receiver = receiver?.let { r -> other.receiver?.let { r.mergeWith(it) } ?: r } ?: other.receiver, + documentation = documentation.mergeWith(other.documentation), + actual = actual.mergeWith(other.actual), + visibility = visibility.mergeWith(other.visibility), + platformData = (platformData + other.platformData).distinct(), + generics = merge(generics + other.generics, TypeParameter::mergeWith) +).mergeExtras(this, other) + +fun Property.mergeWith(other: Property): Property = copy( + receiver = receiver?.let { r -> other.receiver?.let { r.mergeWith(it) } ?: r } ?: other.receiver, + documentation = documentation.mergeWith(other.documentation), + actual = actual.mergeWith(other.actual), + visibility = visibility.mergeWith(other.visibility), + platformData = (platformData + other.platformData).distinct(), + getter = getter.mergeWith(other.getter), + setter = setter?.let { s -> other.setter?.let { s.mergeWith(it) } ?: s } ?: other.setter +).mergeExtras(this, other) + +fun Classlike.setPlatformData(platformData: List): Classlike = when(this) { + is Class -> copy(platformData = platformData) + is Enum -> copy(platformData = platformData) + is Interface -> copy(platformData = platformData) + is Object -> copy(platformData = platformData) + else -> throw IllegalStateException("${this::class.qualifiedName} ${this.name} cannot have platform set") +} + fun Classlike.mergeWith(other: Classlike): Classlike = when { this is Class && other is Class -> mergeWith(other) this is Enum && other is Enum -> mergeWith(other) - else -> throw IllegalStateException("${this::class.qualifiedName} ${this.name} cannot be merged with ${other::class.qualifiedName} ${other.name}") + this is Interface && other is Interface -> mergeWith(other) + this is Object && other is Object -> mergeWith(other) + this is Annotation && other is Annotation -> mergeWith(other) + else -> throw IllegalStateException("${this::class.qualifiedName} ${this.name} cannot be mergesd with ${other::class.qualifiedName} ${other.name}") } -fun Class.mergeWith(other: Class) = Class( - dri = dri, - name = name, - kind = kind, - constructors = merge(constructors + other.constructors, Function::mergeWith), - functions = merge(functions + other.functions, Function::mergeWith), - properties = merge(properties + other.properties, Property::mergeWith), - classlikes = merge(classlikes + other.classlikes, Classlike::mergeWith), - expected = expected?.mergeWith(other.expected), - actual = (actual + other.actual).mergeClassPlatformInfo(), - visibility = (visibility + other.visibility) -) +fun Class.mergeWith(other: Class): Class = copy( + constructors = mergeExpectActual(constructors + other.constructors, Function::mergeWith) { copy(platformData = it) }, + functions = mergeExpectActual(functions + other.functions, Function::mergeWith) { copy(platformData = it) }, + properties = mergeExpectActual(properties + other.properties, Property::mergeWith) { copy(platformData = it) }, + classlikes = mergeExpectActual(classlikes + other.classlikes, Classlike::mergeWith, Classlike::setPlatformData), + companion = companion?.let { c -> other.companion?.let { c.mergeWith(it) } ?: c } ?: other.companion, + generics = merge(generics + other.generics, TypeParameter::mergeWith), + supertypes = supertypes.mergeWith(other.supertypes), + documentation = documentation.mergeWith(other.documentation), + actual = actual.mergeWith(other.actual), + visibility = visibility.mergeWith(other.visibility), + platformData = (platformData + other.platformData).distinct() +).mergeExtras(this, other) -fun Enum.mergeWith(other: Enum) = Enum( - dri = dri, - name = name, - functions = merge(functions + other.functions, Function::mergeWith), - properties = merge(properties + other.properties, Property::mergeWith), - classlikes = merge(classlikes + other.classlikes, Classlike::mergeWith), - expected = expected?.mergeWith(other.expected), - actual = (actual + other.actual).mergeClassPlatformInfo(), - entries = (this.entries + other.entries.distinctBy { it.dri }.toList()), - constructors = merge(constructors + other.constructors, Function::mergeWith), - visibility = visibility -) +fun Enum.mergeWith(other: Enum): Enum = copy( + entries = merge(entries + other.entries, EnumEntry::mergeWith), + constructors = mergeExpectActual(constructors + other.constructors, Function::mergeWith) { copy(platformData = it) }, + functions = mergeExpectActual(functions + other.functions, Function::mergeWith) { copy(platformData = it) }, + properties = mergeExpectActual(properties + other.properties, Property::mergeWith) { copy(platformData = it) }, + classlikes = mergeExpectActual(classlikes + other.classlikes, Classlike::mergeWith, Classlike::setPlatformData), + companion = companion?.let { c -> other.companion?.let { c.mergeWith(it) } ?: c } ?: other.companion, + supertypes = supertypes.mergeWith(other.supertypes), + documentation = documentation.mergeWith(other.documentation), + actual = actual.mergeWith(other.actual), + visibility = visibility.mergeWith(other.visibility), + platformData = (platformData + other.platformData).distinct() +).mergeExtras(this, other) -fun Parameter.mergeWith(other: Parameter) = Parameter( - dri, - name, - type, - expected?.mergeWith(other.expected), - (actual + other.actual).merge() -) +fun EnumEntry.mergeWith(other: EnumEntry): EnumEntry = copy( + functions = mergeExpectActual(functions + other.functions, Function::mergeWith) { copy(platformData = it) }, + properties = mergeExpectActual(properties + other.properties, Property::mergeWith) { copy(platformData = it) }, + classlikes = mergeExpectActual(classlikes + other.classlikes, Classlike::mergeWith, Classlike::setPlatformData), + documentation = documentation.mergeWith(other.documentation), + platformData = (platformData + other.platformData).distinct() +).mergeExtras(this, other) + +fun Object.mergeWith(other: Object): Object = copy( + functions = mergeExpectActual(functions + other.functions, Function::mergeWith) { copy(platformData = it) }, + properties = mergeExpectActual(properties + other.properties, Property::mergeWith) { copy(platformData = it) }, + classlikes = mergeExpectActual(classlikes + other.classlikes, Classlike::mergeWith, Classlike::setPlatformData), + supertypes = supertypes.mergeWith(other.supertypes), + documentation = documentation.mergeWith(other.documentation), + actual = actual.mergeWith(other.actual), + visibility = visibility.mergeWith(other.visibility), + platformData = (platformData + other.platformData).distinct() +).mergeExtras(this, other) + +fun Interface.mergeWith(other: Interface): Interface = copy( + functions = mergeExpectActual(functions + other.functions, Function::mergeWith) { copy(platformData = it) }, + properties = mergeExpectActual(properties + other.properties, Property::mergeWith) { copy(platformData = it) }, + classlikes = mergeExpectActual(classlikes + other.classlikes, Classlike::mergeWith, Classlike::setPlatformData), + companion = companion?.let { c -> other.companion?.let { c.mergeWith(it) } ?: c } ?: other.companion, + generics = merge(generics + other.generics, TypeParameter::mergeWith), + supertypes = supertypes.mergeWith(other.supertypes), + documentation = documentation.mergeWith(other.documentation), + actual = actual.mergeWith(other.actual), + visibility = visibility.mergeWith(other.visibility), + platformData = (platformData + other.platformData).distinct() +).mergeExtras(this, other) + +fun Annotation.mergeWith(other: Annotation): Annotation = copy( + constructors = mergeExpectActual(constructors + other.constructors, Function::mergeWith) { copy(platformData = it) }, + functions = mergeExpectActual(functions + other.functions, Function::mergeWith) { copy(platformData = it) }, + properties = mergeExpectActual(properties + other.properties, Property::mergeWith) { copy(platformData = it) }, + classlikes = mergeExpectActual(classlikes + other.classlikes, Classlike::mergeWith, Classlike::setPlatformData), + companion = companion?.let { c -> other.companion?.let { c.mergeWith(it) } ?: c } ?: other.companion, + documentation = documentation.mergeWith(other.documentation), + actual = actual.mergeWith(other.actual), + visibility = visibility.mergeWith(other.visibility), + platformData = (platformData + other.platformData).distinct() +).mergeExtras(this, other) + +fun Parameter.mergeWith(other: Parameter): Parameter = copy( + documentation = documentation.mergeWith(other.documentation), + platformData = (platformData + other.platformData).distinct() +).mergeExtras(this, other) -fun Package.mergeWith(other: Package): Package = Package( - dri, - merge(functions + other.functions, Function::mergeWith), - merge(properties + other.properties, Property::mergeWith), - merge(classlikes + other.classlikes, Classlike::mergeWith) -) \ No newline at end of file +fun TypeParameter.mergeWith(other: TypeParameter): TypeParameter = copy( + documentation = documentation.mergeWith(other.documentation), + platformData = (platformData + other.platformData).distinct() +).mergeExtras(this, other) \ No newline at end of file -- cgit