aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src/main/kotlin/transformers
diff options
context:
space:
mode:
authorsebastian.sellmair <sebastian.sellmair@jetbrains.com>2020-06-05 09:07:20 +0200
committerPaweł Marks <Kordyjan@users.noreply.github.com>2020-06-10 10:52:43 +0200
commite9fd8b7bc00491b50e4822acc82e5615ab0bde3b (patch)
tree27648e766764df986a154e6c3353d634d219c0bc /plugins/base/src/main/kotlin/transformers
parent77c8777b7f66bddd374d68decd507547d356d602 (diff)
downloaddokka-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.kt157
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 }
+ }
+}