diff options
9 files changed, 379 insertions, 7 deletions
diff --git a/core/src/main/kotlin/CoreExtensions.kt b/core/src/main/kotlin/CoreExtensions.kt index be354bc4..d7b0b285 100644 --- a/core/src/main/kotlin/CoreExtensions.kt +++ b/core/src/main/kotlin/CoreExtensions.kt @@ -6,6 +6,7 @@ import org.jetbrains.dokka.transformers.descriptors.DescriptorToDocumentableTran import org.jetbrains.dokka.transformers.documentation.DocumentableMerger import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator import org.jetbrains.dokka.transformers.documentation.DocumentableTransformer +import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer import org.jetbrains.dokka.transformers.pages.PageTransformer import org.jetbrains.dokka.transformers.psi.PsiToDocumentableTranslator import kotlin.reflect.KProperty @@ -13,6 +14,7 @@ import kotlin.reflect.KProperty object CoreExtensions { val descriptorToDocumentableTranslator by coreExtension<DescriptorToDocumentableTranslator>() val psiToDocumentableTranslator by coreExtension<PsiToDocumentableTranslator>() + val preMergeDocumentableTransformer by coreExtension<PreMergeDocumentableTransformer>() val documentableMerger by coreExtension<DocumentableMerger>() val documentableTransformer by coreExtension<DocumentableTransformer>() val documentableToPageTranslator by coreExtension<DocumentableToPageTranslator>() diff --git a/core/src/main/kotlin/DokkaGenerator.kt b/core/src/main/kotlin/DokkaGenerator.kt index a260d5ad..d598c773 100644 --- a/core/src/main/kotlin/DokkaGenerator.kt +++ b/core/src/main/kotlin/DokkaGenerator.kt @@ -39,11 +39,14 @@ class DokkaGenerator( report("Creating documentation models") val modulesFromPlatforms = createDocumentationModels(platforms, context) + report("Transforming documentation model before merging") + val transformedDocumentationBeforeMerge = transformDocumentationModelBeforeMerge(modulesFromPlatforms, context) + report("Merging documentation models") - val documentationModel = mergeDocumentationModels(modulesFromPlatforms, context) + val documentationModel = mergeDocumentationModels(transformedDocumentationBeforeMerge, context) - report("Transforming documentation model") - val transformedDocumentation = transformDocumentationModel(documentationModel, context) + report("Transforming documentation model after merging") + val transformedDocumentation = transformDocumentationModelAfterMerge(documentationModel, context) report("Creating pages") val pages = createPages(transformedDocumentation, context) @@ -61,7 +64,7 @@ class DokkaGenerator( fun setUpAnalysis(configuration: DokkaConfiguration): Map<PlatformData, EnvironmentAndFacade> = configuration.passesConfigurations.map { - PlatformData(it.moduleName, it.analysisPlatform, it.targets) to createEnvironmentAndFacade(it) + it.platformData to createEnvironmentAndFacade(it) }.toMap() fun initializePlugins( @@ -77,12 +80,17 @@ class DokkaGenerator( ) = platforms.map { (pdata, _) -> translateDescriptors(pdata, context) } + platforms.map { (pdata, _) -> translatePsi(pdata, context) } + fun transformDocumentationModelBeforeMerge( + modulesFromPlatforms: List<DModule>, + context: DokkaContext + ) = context[CoreExtensions.preMergeDocumentableTransformer].fold(modulesFromPlatforms) { acc, t -> t(acc, context) } + fun mergeDocumentationModels( modulesFromPlatforms: List<DModule>, context: DokkaContext ) = context.single(CoreExtensions.documentableMerger).invoke(modulesFromPlatforms, context) - fun transformDocumentationModel( + fun transformDocumentationModelAfterMerge( documentationModel: DModule, context: DokkaContext ) = context[CoreExtensions.documentableTransformer].fold(documentationModel) { acc, t -> t(acc, context) } diff --git a/core/src/main/kotlin/configuration.kt b/core/src/main/kotlin/configuration.kt index 8c6d35e8..4a6b7d68 100644 --- a/core/src/main/kotlin/configuration.kt +++ b/core/src/main/kotlin/configuration.kt @@ -1,5 +1,6 @@ package org.jetbrains.dokka +import org.jetbrains.dokka.pages.PlatformData import java.io.File import java.net.URL @@ -57,6 +58,9 @@ interface DokkaConfiguration { val analysisPlatform: Platform val targets: List<String> val sinceKotlin: String? + + val platformData: PlatformData + get() = PlatformData(moduleName, analysisPlatform, targets) } interface SourceRoot { diff --git a/core/src/main/kotlin/model/documentableUtils.kt b/core/src/main/kotlin/model/documentableUtils.kt new file mode 100644 index 00000000..7f946344 --- /dev/null +++ b/core/src/main/kotlin/model/documentableUtils.kt @@ -0,0 +1,23 @@ +package org.jetbrains.dokka.model + +import org.jetbrains.dokka.pages.PlatformData + +fun <T> PlatformDependent<T>.filtered(platformDataList: List<PlatformData>) = PlatformDependent( + map.filter { it.key in platformDataList }, + expect +) + +fun DTypeParameter.filter(filteredData: List<PlatformData>) = + if (filteredData.containsAll(platformData)) this + else { + val intersection = filteredData.intersect(platformData).toList() + if (intersection.isEmpty()) null + else DTypeParameter( + dri, + name, + documentation.filtered(intersection), + bounds, + intersection, + extra + ) + }
\ No newline at end of file diff --git a/core/src/main/kotlin/transformers/documentation/PreMergeDocumentableTransformer.kt b/core/src/main/kotlin/transformers/documentation/PreMergeDocumentableTransformer.kt new file mode 100644 index 00000000..dfb1f26b --- /dev/null +++ b/core/src/main/kotlin/transformers/documentation/PreMergeDocumentableTransformer.kt @@ -0,0 +1,8 @@ +package org.jetbrains.dokka.transformers.documentation + +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.plugability.DokkaContext + +interface PreMergeDocumentableTransformer { + operator fun invoke(modules: List<DModule>, context: DokkaContext): List<DModule> +}
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt index 8934dd7f..448373ea 100644 --- a/plugins/base/src/main/kotlin/DokkaBase.kt +++ b/plugins/base/src/main/kotlin/DokkaBase.kt @@ -11,6 +11,7 @@ import org.jetbrains.dokka.base.signatures.SignatureProvider import org.jetbrains.dokka.base.transformers.documentables.DefaultDocumentableMerger import org.jetbrains.dokka.base.transformers.documentables.InheritorsExtractorTransformer import org.jetbrains.dokka.base.transformers.pages.annotations.DeprecatedStrikethroughTransformer +import org.jetbrains.dokka.base.transformers.documentables.DocumentableVisibilityFilter import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter import org.jetbrains.dokka.base.transformers.pages.merger.FallbackPageMergerStrategy @@ -43,6 +44,10 @@ class DokkaBase : DokkaPlugin() { CoreExtensions.documentableMerger with DefaultDocumentableMerger } + val preMergeDocumentableTransformer by extending(isFallback = true) { + CoreExtensions.preMergeDocumentableTransformer with DocumentableVisibilityFilter + } + val kotlinSignatureProvider by extending(isFallback = true) { signatureProvider providing { ctx -> KotlinSignatureProvider(ctx.single(commentsToContentConverter), ctx.logger) diff --git a/plugins/base/src/main/kotlin/transformers/documentables/DocumentableVisibilityFilter.kt b/plugins/base/src/main/kotlin/transformers/documentables/DocumentableVisibilityFilter.kt new file mode 100644 index 00000000..76276f39 --- /dev/null +++ b/plugins/base/src/main/kotlin/transformers/documentables/DocumentableVisibilityFilter.kt @@ -0,0 +1,316 @@ +package org.jetbrains.dokka.base.transformers.documentables + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.DAnnotation +import org.jetbrains.dokka.model.DEnum +import org.jetbrains.dokka.model.DFunction +import org.jetbrains.dokka.pages.PlatformData +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer + +internal object DocumentableVisibilityFilter : PreMergeDocumentableTransformer { + + override fun invoke(modules: List<DModule>, context: DokkaContext): List<DModule> = modules.map { original -> + val packageOptions = + context.configuration.passesConfigurations.first { original.platformData.contains(it.platformData) } + .perPackageOptions + DocumentableFilter(packageOptions).processModule(original) + } + + private class DocumentableFilter(val packageOptions: List<DokkaConfiguration.PackageOptions>) { + + fun Visibility.isAllowedInPackage(packageName: String?) = when (this) { + is JavaVisibility.Public, + is JavaVisibility.Default, + is KotlinVisibility.Public -> true + else -> packageName != null && packageOptions.firstOrNull { packageName.startsWith(it.prefix) }?.includeNonPublic == true + } + + fun processModule(original: DModule) = + filterPackages(original.packages).let { (modified, packages) -> + if (!modified) original + else + DModule( + original.name, + packages = packages, + documentation = original.documentation, + platformData = original.platformData, + extra = original.extra + ) + } + + private fun filterPackages(packages: List<DPackage>): Pair<Boolean, List<DPackage>> { + var packagesListChanged = false + val filteredPackages = packages.mapNotNull { + var modified = false + val functions = filterFunctions(it.functions).let { (listModified, list) -> + modified = modified || listModified + list + } + val properties = filterProperties(it.properties).let { (listModified, list) -> + modified = modified || listModified + list + } + val classlikes = filterClasslikes(it.classlikes).let { (listModified, list) -> + modified = modified || listModified + list + } + when { + !modified -> it + functions.isEmpty() && properties.isEmpty() && classlikes.isEmpty() -> null + else -> { + packagesListChanged = true + DPackage( + it.dri, + functions, + properties, + classlikes, + it.documentation, + it.platformData, + it.extra + ) + } + } + } + return Pair(packagesListChanged, filteredPackages) + } + + private fun <T : WithVisibility> alwaysTrue(a: T, p: PlatformData) = true + private fun <T : WithVisibility> alwaysFalse(a: T, p: PlatformData) = false + + private fun <T> T.filterPlatforms( + additionalCondition: (T, PlatformData) -> Boolean = ::alwaysTrue, + optionalCondition: (T, PlatformData) -> Boolean = ::alwaysFalse + ) where T : Documentable, T : WithVisibility = + visibility.mapNotNull { (platformData, visibility) -> + platformData.takeIf { d -> + (visibility.isAllowedInPackage(dri.packageName) || optionalCondition( + this, + d + )) && additionalCondition(this, d) + } + } + + private fun <T> List<T>.transform( + additionalCondition: (T, PlatformData) -> Boolean = ::alwaysTrue, + optionalCondition: (T, PlatformData) -> Boolean = ::alwaysFalse, + recreate: (T, List<PlatformData>) -> T + ): Pair<Boolean, List<T>> where T : Documentable, T : WithVisibility { + var changed = false + val values = mapNotNull { t -> + val filteredPlatforms = t.filterPlatforms(additionalCondition, optionalCondition) + when (filteredPlatforms.size) { + t.visibility.size -> t + 0 -> { + changed = true + null + } + else -> { + changed = true + recreate(t, filteredPlatforms) + } + } + } + return Pair(changed, values) + } + + private fun filterFunctions( + functions: List<DFunction>, + additionalCondition: (DFunction, PlatformData) -> Boolean = ::alwaysTrue + ) = + functions.transform(additionalCondition) { original, filteredPlatforms -> + with(original) { + DFunction( + dri, + name, + isConstructor, + parameters, + documentation.filtered(filteredPlatforms), + sources.filtered(filteredPlatforms), + visibility.filtered(filteredPlatforms), + type, + generics.mapNotNull { it.filter(filteredPlatforms) }, + receiver, + modifier, + filteredPlatforms, + extra + ) + } + } + + private fun hasVisibleAccessorsForPlatform(property: DProperty, data: PlatformData) = + property.getter?.visibility?.get(data)?.isAllowedInPackage(property.dri.packageName) == true || + property.setter?.visibility?.get(data)?.isAllowedInPackage(property.dri.packageName) == true + + private fun filterProperties( + properties: List<DProperty>, + additionalCondition: (DProperty, PlatformData) -> Boolean = ::alwaysTrue + ): Pair<Boolean, List<DProperty>> = + properties.transform(additionalCondition, ::hasVisibleAccessorsForPlatform) { original, filteredPlatforms -> + with(original) { + DProperty( + dri, + name, + documentation.filtered(filteredPlatforms), + sources.filtered(filteredPlatforms), + visibility.filtered(filteredPlatforms), + type, + receiver, + setter, + getter, + modifier, + filteredPlatforms, + extra + ) + } + } + + private fun filterEnumEntries(entries: List<DEnumEntry>, filteredPlatforms: List<PlatformData>) = + entries.mapNotNull { entry -> + if (filteredPlatforms.containsAll(entry.platformData)) entry + else { + val intersection = filteredPlatforms.intersect(entry.platformData).toList() + if (intersection.isEmpty()) null + else DEnumEntry( + entry.dri, + entry.name, + entry.documentation.filtered(intersection), + filterFunctions(entry.functions) { _, data -> data in intersection }.second, + filterProperties(entry.properties) { _, data -> data in intersection }.second, + filterClasslikes(entry.classlikes) { _, data -> data in intersection }.second, + intersection, + entry.extra + ) + } + } + + private fun filterClasslikes( + classlikeList: List<DClasslike>, + additionalCondition: (DClasslike, PlatformData) -> Boolean = ::alwaysTrue + ): Pair<Boolean, List<DClasslike>> { + var classlikesListChanged = false + val filteredClasslikes: List<DClasslike> = classlikeList.mapNotNull { + with(it) { + val filteredPlatforms = filterPlatforms(additionalCondition) + if (filteredPlatforms.isEmpty()) { + classlikesListChanged = true + null + } else { + var modified = platformData.size != filteredPlatforms.size + val functions = + filterFunctions(functions) { _, data -> data in filteredPlatforms }.let { (listModified, list) -> + modified = modified || listModified + list + } + val properties = + filterProperties(properties) { _, data -> data in filteredPlatforms }.let { (listModified, list) -> + modified = modified || listModified + list + } + val classlikes = + filterClasslikes(classlikes) { _, data -> data in filteredPlatforms }.let { (listModified, list) -> + modified = modified || listModified + list + } + val companion = + if (this is WithCompanion) filterClasslikes(listOfNotNull(companion)) { _, data -> data in filteredPlatforms }.let { (listModified, list) -> + modified = modified || listModified + list.firstOrNull() as DObject? + } else null + val constructors = if (this is WithConstructors) + filterFunctions(constructors) { _, data -> data in filteredPlatforms }.let { (listModified, list) -> + modified = modified || listModified + list + } else emptyList() + val generics = + if (this is WithGenerics) generics.mapNotNull { param -> param.filter(filteredPlatforms) } else emptyList() + val enumEntries = + if (this is DEnum) filterEnumEntries(entries, filteredPlatforms) else emptyList() + classlikesListChanged = classlikesListChanged || modified + when { + !modified -> this + this is DClass -> DClass( + dri, + name, + constructors, + functions, + properties, + classlikes, + sources.filtered(filteredPlatforms), + visibility.filtered(filteredPlatforms), + companion, + generics, + supertypes.filtered(filteredPlatforms), + documentation.filtered(filteredPlatforms), + modifier, + filteredPlatforms, + extra + ) + this is DAnnotation -> DAnnotation( + name, + dri, + documentation.filtered(filteredPlatforms), + sources.filtered(filteredPlatforms), + functions, + properties, + classlikes, + visibility.filtered(filteredPlatforms), + companion, + constructors, + filteredPlatforms, + extra + ) + this is DEnum -> DEnum( + dri, + name, + enumEntries, + documentation.filtered(filteredPlatforms), + sources.filtered(filteredPlatforms), + functions, + properties, + classlikes, + visibility.filtered(filteredPlatforms), + companion, + constructors, + supertypes.filtered(filteredPlatforms), + filteredPlatforms, + extra + ) + this is DInterface -> DInterface( + dri, + name, + documentation.filtered(filteredPlatforms), + sources.filtered(filteredPlatforms), + functions, + properties, + classlikes, + visibility.filtered(filteredPlatforms), + companion, + generics, + supertypes.filtered(filteredPlatforms), + filteredPlatforms, + extra + ) + this is DObject -> DObject( + name, + dri, + documentation.filtered(filteredPlatforms), + sources.filtered(filteredPlatforms), + functions, + properties, + classlikes, + visibility, + supertypes.filtered(filteredPlatforms), + filteredPlatforms, + extra + ) + else -> null + } + } + } + } + return Pair(classlikesListChanged, filteredClasslikes) + } + } +}
\ No newline at end of file diff --git a/testApi/src/main/kotlin/testApi/testRunner/DokkaTestGenerator.kt b/testApi/src/main/kotlin/testApi/testRunner/DokkaTestGenerator.kt index ddee7083..0e77344d 100644 --- a/testApi/src/main/kotlin/testApi/testRunner/DokkaTestGenerator.kt +++ b/testApi/src/main/kotlin/testApi/testRunner/DokkaTestGenerator.kt @@ -26,10 +26,13 @@ internal class DokkaTestGenerator( val modulesFromPlatforms = dokkaGenerator.createDocumentationModels(platforms, context) documentablesCreationStage(modulesFromPlatforms) - val documentationModel = dokkaGenerator.mergeDocumentationModels(modulesFromPlatforms, context) + val filteredModules = dokkaGenerator.transformDocumentationModelBeforeMerge(modulesFromPlatforms, context) + documentablesFirstTransformationStep(filteredModules) + + val documentationModel = dokkaGenerator.mergeDocumentationModels(filteredModules, context) documentablesMergingStage(documentationModel) - val transformedDocumentation = dokkaGenerator.transformDocumentationModel(documentationModel, context) + val transformedDocumentation = dokkaGenerator.transformDocumentationModelAfterMerge(documentationModel, context) documentablesTransformationStage(transformedDocumentation) val pages = dokkaGenerator.createPages(transformedDocumentation, context) diff --git a/testApi/src/main/kotlin/testApi/testRunner/TestRunner.kt b/testApi/src/main/kotlin/testApi/testRunner/TestRunner.kt index a0e3b709..64d16fef 100644 --- a/testApi/src/main/kotlin/testApi/testRunner/TestRunner.kt +++ b/testApi/src/main/kotlin/testApi/testRunner/TestRunner.kt @@ -105,6 +105,7 @@ abstract class AbstractCoreTest { var analysisSetupStage: (Map<PlatformData, EnvironmentAndFacade>) -> Unit = {} var pluginsSetupStage: (DokkaContext) -> Unit = {} var documentablesCreationStage: (List<DModule>) -> Unit = {} + var documentablesFirstTransformationStep: (List<DModule>) -> Unit = {} var documentablesMergingStage: (DModule) -> Unit = {} var documentablesTransformationStage: (DModule) -> Unit = {} var pagesGenerationStage: (ModulePageNode) -> Unit = {} @@ -115,6 +116,7 @@ abstract class AbstractCoreTest { analysisSetupStage, pluginsSetupStage, documentablesCreationStage, + documentablesFirstTransformationStep, documentablesMergingStage, documentablesTransformationStage, pagesGenerationStage, @@ -217,6 +219,7 @@ data class TestMethods( val analysisSetupStage: (Map<PlatformData, EnvironmentAndFacade>) -> Unit, val pluginsSetupStage: (DokkaContext) -> Unit, val documentablesCreationStage: (List<DModule>) -> Unit, + val documentablesFirstTransformationStep: (List<DModule>) -> Unit, val documentablesMergingStage: (DModule) -> Unit, val documentablesTransformationStage: (DModule) -> Unit, val pagesGenerationStage: (ModulePageNode) -> Unit, |