aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src/main/kotlin
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
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')
-rw-r--r--plugins/base/src/main/kotlin/DokkaBase.kt12
-rw-r--r--plugins/base/src/main/kotlin/transformers/documentables/ReportUndocumentedTransformer.kt157
-rw-r--r--plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt22
-rw-r--r--plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt37
4 files changed, 210 insertions, 18 deletions
diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt
index c99372af..7a969b91 100644
--- a/plugins/base/src/main/kotlin/DokkaBase.kt
+++ b/plugins/base/src/main/kotlin/DokkaBase.kt
@@ -9,12 +9,12 @@ import org.jetbrains.dokka.base.signatures.SignatureProvider
import org.jetbrains.dokka.base.resolvers.external.*
import org.jetbrains.dokka.base.resolvers.local.DefaultLocationProviderFactory
import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory
-import org.jetbrains.dokka.base.transformers.documentables.ActualTypealiasAdder
+import org.jetbrains.dokka.base.transformers.documentables.*
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.documentables.ModuleAndPackageDocumentationTransformer
+import org.jetbrains.dokka.base.transformers.documentables.ReportUndocumentedTransformer
+import org.jetbrains.dokka.base.transformers.pages.annotations.DeprecatedStrikethroughTransformer
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
@@ -73,6 +73,10 @@ class DokkaBase : DokkaPlugin() {
CoreExtensions.documentableTransformer with ActualTypealiasAdder()
}
+ val undocumentedCodeReporter by extending {
+ CoreExtensions.documentableTransformer with ReportUndocumentedTransformer()
+ }
+
val documentableToPageTranslator by extending(isFallback = true) {
CoreExtensions.documentableToPageTranslator providing { ctx ->
DefaultDocumentableToPageTranslator(
@@ -175,7 +179,7 @@ class DokkaBase : DokkaPlugin() {
}
val sourcesetDependencyAppender by extending {
- htmlPreprocessors providing ::SourcesetDependencyAppender order { after(rootCreator)}
+ htmlPreprocessors providing ::SourcesetDependencyAppender order { after(rootCreator) }
}
val allModulePageCreators by extending {
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 }
+ }
+}
diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt
index f3b047bc..9f01267e 100644
--- a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt
+++ b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt
@@ -1,6 +1,5 @@
package org.jetbrains.dokka.base.translators.descriptors
-import org.jetbrains.kotlin.descriptors.annotations.Annotated
import org.jetbrains.dokka.analysis.DokkaResolutionFacade
import org.jetbrains.dokka.links.*
import org.jetbrains.dokka.links.Callable
@@ -11,15 +10,16 @@ import org.jetbrains.dokka.model.doc.*
import org.jetbrains.dokka.model.properties.PropertyContainer
import org.jetbrains.dokka.parsers.MarkdownParser
import org.jetbrains.dokka.plugability.DokkaContext
-import org.jetbrains.dokka.utilities.DokkaLogger
import org.jetbrains.dokka.transformers.sources.SourceToDocumentableTranslator
-import org.jetbrains.kotlin.asJava.classes.tryResolveMarkerInterfaceFQName
+import org.jetbrains.dokka.utilities.DokkaLogger
import org.jetbrains.kotlin.builtins.isExtensionFunctionType
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.codegen.isJvmStaticInObjectOrClassOrInterface
import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.FAKE_OVERRIDE
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Visibility
+import org.jetbrains.kotlin.descriptors.annotations.Annotated
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBodies
import org.jetbrains.kotlin.idea.kdoc.findKDoc
@@ -29,15 +29,10 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentsInParentheses
import org.jetbrains.kotlin.resolve.calls.components.isVararg
import org.jetbrains.kotlin.resolve.constants.ConstantValue
-import org.jetbrains.kotlin.resolve.constants.AnnotationValue as ConstantsAnnotationValue
-import org.jetbrains.kotlin.resolve.constants.ArrayValue as ConstantsArrayValue
-import org.jetbrains.kotlin.resolve.constants.EnumValue as ConstantsEnumValue
-import org.jetbrains.kotlin.resolve.constants.KClassValue as ConstantsKtClassValue
-import org.jetbrains.kotlin.resolve.constants.KClassValue.Value.NormalClass
import org.jetbrains.kotlin.resolve.constants.KClassValue.Value.LocalClass
+import org.jetbrains.kotlin.resolve.constants.KClassValue.Value.NormalClass
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny
-import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.MemberScope
@@ -46,12 +41,13 @@ import org.jetbrains.kotlin.resolve.source.PsiSourceElement
import org.jetbrains.kotlin.types.DynamicType
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeProjection
-import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.nio.file.Paths
-import kotlin.IllegalArgumentException
-import kotlin.reflect.jvm.internal.impl.resolve.constants.KClassValue
+import org.jetbrains.kotlin.resolve.constants.AnnotationValue as ConstantsAnnotationValue
+import org.jetbrains.kotlin.resolve.constants.ArrayValue as ConstantsArrayValue
+import org.jetbrains.kotlin.resolve.constants.EnumValue as ConstantsEnumValue
+import org.jetbrains.kotlin.resolve.constants.KClassValue as ConstantsKtClassValue
object DefaultDescriptorToDocumentableTranslator : SourceToDocumentableTranslator {
@@ -272,7 +268,7 @@ private class DokkaDescriptorVisitor(
supertypes = info.supertypes.toSourceSetDependent(),
generics = descriptor.declaredTypeParameters.map { it.toTypeParameter() },
documentation = info.docs,
- modifier = descriptor.modifier().toSourceSetDependent(),
+ modifier = descriptor.modifier().toSourceSetDependent(),
companion = descriptor.companion(driWithPlatform),
sourceSets = listOf(sourceSet),
extra = PropertyContainer.withAll(descriptor.additionalExtras(), descriptor.getAnnotations())
diff --git a/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt b/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt
index 2698fd0a..e77c06a4 100644
--- a/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt
+++ b/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt
@@ -1,6 +1,7 @@
package org.jetbrains.dokka.base.translators.psi
import com.intellij.psi.*
+import com.intellij.psi.impl.source.PsiFieldImpl
import com.intellij.psi.impl.source.javadoc.PsiDocParamRef
import com.intellij.psi.impl.source.tree.JavaDocElementType
import com.intellij.psi.impl.source.tree.LeafPsiElement
@@ -26,7 +27,7 @@ class JavadocParser(
) : JavaDocumentationParser {
override fun parseDocumentation(element: PsiNamedElement): DocumentationNode {
- val docComment = (element as? PsiDocCommentOwner)?.docComment ?: return DocumentationNode(emptyList())
+ val docComment = findClosestDocComment(element) ?: return DocumentationNode(emptyList())
val nodes = mutableListOf<TagWrapper>()
docComment.getDescription()?.let { nodes.add(it) }
nodes.addAll(docComment.tags.mapNotNull { tag ->
@@ -43,6 +44,40 @@ class JavadocParser(
return DocumentationNode(nodes)
}
+ private fun findClosestDocComment(element: PsiNamedElement): PsiDocComment? {
+ (element as? PsiDocCommentOwner)?.docComment?.run { return this }
+ if (element is PsiMethod) {
+ val superMethods = element.findSuperMethods()
+ if (superMethods.isEmpty()) return null
+
+ if (superMethods.size == 1) {
+ return findClosestDocComment(superMethods.single())
+ }
+
+ val superMethodDocumentation = superMethods.map(::findClosestDocComment)
+ if (superMethodDocumentation.size == 1) {
+ return superMethodDocumentation.single()
+ }
+
+ logger.warn(
+ "Conflicting documentation for ${DRI.from(element)}" +
+ "${superMethods.map { DRI.from(it) }}"
+ )
+
+ /* Prioritize super class over interface */
+ val indexOfSuperClass = superMethods.indexOfFirst { method ->
+ val parent = method.parent
+ if (parent is PsiClass) !parent.isInterface
+ else false
+ }
+
+ return if (indexOfSuperClass >= 0) superMethodDocumentation[indexOfSuperClass]
+ else superMethodDocumentation.first()
+ }
+
+ return null
+ }
+
private fun getSeeTagElementContent(tag: PsiDocTag): List<DocTag> =
listOfNotNull(tag.referenceElement()?.toDocumentationLink())