diff options
| author | sebastian.sellmair <sebastian.sellmair@jetbrains.com> | 2020-06-05 09:07:20 +0200 |
|---|---|---|
| committer | Paweł Marks <Kordyjan@users.noreply.github.com> | 2020-06-10 10:52:43 +0200 |
| commit | e9fd8b7bc00491b50e4822acc82e5615ab0bde3b (patch) | |
| tree | 27648e766764df986a154e6c3353d634d219c0bc /plugins/base/src/main/kotlin/transformers | |
| parent | 77c8777b7f66bddd374d68decd507547d356d602 (diff) | |
| download | dokka-e9fd8b7bc00491b50e4822acc82e5615ab0bde3b.tar.gz dokka-e9fd8b7bc00491b50e4822acc82e5615ab0bde3b.tar.bz2 dokka-e9fd8b7bc00491b50e4822acc82e5615ab0bde3b.zip | |
Implement `reportUndocumented` option to report undocumented code
Diffstat (limited to 'plugins/base/src/main/kotlin/transformers')
| -rw-r--r-- | plugins/base/src/main/kotlin/transformers/documentables/ReportUndocumentedTransformer.kt | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/plugins/base/src/main/kotlin/transformers/documentables/ReportUndocumentedTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/ReportUndocumentedTransformer.kt new file mode 100644 index 00000000..4aa7632e --- /dev/null +++ b/plugins/base/src/main/kotlin/transformers/documentables/ReportUndocumentedTransformer.kt @@ -0,0 +1,157 @@ +package org.jetbrains.dokka.base.transformers.documentables + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.DokkaConfiguration.PassConfiguration +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.transformers.documentation.DocumentableTransformer +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.FAKE_OVERRIDE +import org.jetbrains.kotlin.utils.addToStdlib.safeAs + +internal class ReportUndocumentedTransformer : DocumentableTransformer { + + override fun invoke(original: DModule, context: DokkaContext): DModule = original.apply { + withDescendants().forEach { documentable -> invoke(documentable, context) } + } + + private fun invoke(documentable: Documentable, context: DokkaContext) { + documentable.sourceSets.forEach { sourceSet -> + if (shouldBeReportedIfNotDocumented(documentable, sourceSet, context)) { + reportIfUndocumented(context, documentable, sourceSet) + } + } + } + + private fun shouldBeReportedIfNotDocumented( + documentable: Documentable, sourceSet: SourceSetData, context: DokkaContext + ): Boolean { + val passConfiguration = passConfiguration(context, sourceSet) + val packageOptionsOrNull = packageOptionsOrNull(passConfiguration, documentable) + + if (!(packageOptionsOrNull?.reportUndocumented ?: passConfiguration.reportUndocumented)) { + return false + } + + if (documentable is DParameter || documentable is DPackage || documentable is DModule) { + return false + } + + if (isConstructor(documentable)) { + return false + } + + if (isFakeOverride(documentable, sourceSet)) { + return false + } + + if (isPrivateOrInternalApi(documentable, sourceSet)) { + return false + } + + return true + } + + private fun reportIfUndocumented( + context: DokkaContext, + documentable: Documentable, + sourceSet: SourceSetData + ) { + if (isUndocumented(documentable, sourceSet)) { + val documentableDescription = with(documentable) { + buildString { + dri.packageName?.run { + append(this) + append("/") + } + + dri.classNames?.run { + append(this) + append("/") + } + + dri.callable?.run { + append(name) + append("/") + append(signature()) + append("/") + } + + val sourceSetName = sourceSet.sourceSetName + if (sourceSetName != null.toString()) { + append(" ($sourceSetName)") + } + } + } + + context.logger.warn("Undocumented: $documentableDescription") + } + } + + private fun isUndocumented(documentable: Documentable, sourceSet: SourceSetData): Boolean { + fun resolveDependentSourceSets(sourceSet: SourceSetData): List<SourceSetData> { + return sourceSet.dependentSourceSets.map { sourceSetName -> + documentable.sourceSets.single { it.sourceSetName == sourceSetName } + } + } + + fun flatDependentSourceSetsTree(sourceSet: SourceSetData): List<SourceSetData> { + return listOf(sourceSet) + resolveDependentSourceSets(sourceSet) + .flatMap { resolveDependentSourceSets -> flatDependentSourceSetsTree(resolveDependentSourceSets) } + } + + return flatDependentSourceSetsTree(sourceSet).all { sourceSetOrDependentSourceSet -> + documentable.documentation[sourceSetOrDependentSourceSet]?.children?.isEmpty() ?: true + } + } + + private fun isConstructor(documentable: Documentable): Boolean { + if (documentable !is DFunction) return false + return documentable.isConstructor + } + + private fun passConfiguration(context: DokkaContext, sourceSet: SourceSetData): PassConfiguration { + return context.configuration.passesConfigurations.single { configuration -> + // TODO: Use sourceSetID after gradle-rewrite + configuration.sourceSetName == sourceSet.sourceSetName && + configuration.analysisPlatform == sourceSet.platform + } + } + + private fun isFakeOverride(documentable: Documentable, sourceSet: SourceSetData): Boolean { + if (documentable is WithExpectActual) { + val callableMemberDescriptor = documentable.sources[sourceSet] + .safeAs<DescriptorDocumentableSource>()?.descriptor + .safeAs<CallableMemberDescriptor>() + + if (callableMemberDescriptor?.kind == FAKE_OVERRIDE) { + return true + } + } + return false + } + + private fun isPrivateOrInternalApi(documentable: Documentable, sourceSet: SourceSetData): Boolean { + return when (documentable.safeAs<WithVisibility>()?.visibility?.get(sourceSet)) { + KotlinVisibility.Public -> false + KotlinVisibility.Private -> true + KotlinVisibility.Protected -> true + KotlinVisibility.Internal -> true + JavaVisibility.Public -> false + JavaVisibility.Private -> true + JavaVisibility.Protected -> true + JavaVisibility.Default -> true + null -> false + } + } + + private fun packageOptionsOrNull( + passConfiguration: PassConfiguration, + documentable: Documentable + ): DokkaConfiguration.PackageOptions? { + val packageName = documentable.dri.packageName ?: return null + return passConfiguration.perPackageOptions + .filter { packageOptions -> packageName.startsWith(packageOptions.prefix) } + .maxBy { packageOptions -> packageOptions.prefix.length } + } +} |
