Fix Missing documentation inspection for primary ctor properties

#KT-20954 fixed
#KT-21005 fixed
This commit is contained in:
Simon Ogorodnik
2018-02-08 17:49:06 +03:00
parent 0f130a0a5a
commit 470a4bb615
9 changed files with 113 additions and 64 deletions
@@ -17,56 +17,79 @@
package org.jetbrains.kotlin.idea.kdoc package org.jetbrains.kotlin.idea.kdoc
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
import org.jetbrains.kotlin.kdoc.psi.api.KDoc import org.jetbrains.kotlin.kdoc.psi.api.KDoc
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtPrimaryConstructor import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
fun DeclarationDescriptor.findKDoc( fun DeclarationDescriptor.findKDoc(
descriptorToPsi: (DeclarationDescriptorWithSource) -> PsiElement? = { DescriptorToSourceUtils.descriptorToDeclaration(it) } descriptorToPsi: (DeclarationDescriptorWithSource) -> PsiElement? = { DescriptorToSourceUtils.descriptorToDeclaration(it) }
): KDocTag? { ): KDocTag? {
if (this is DeclarationDescriptorWithSource) { if (this is DeclarationDescriptorWithSource) {
var psiDeclaration = descriptorToPsi(this)?.navigationElement val psiDeclaration = descriptorToPsi(this)?.navigationElement
// KDoc for primary constructor is located inside of its class KDoc return (psiDeclaration as? KtElement)?.findKDoc(descriptorToPsi)
if (psiDeclaration is KtPrimaryConstructor) { }
psiDeclaration = psiDeclaration.getContainingClassOrObject() return null
} }
if (psiDeclaration is KtDeclaration) {
val kdoc = psiDeclaration.docComment fun KtElement.findKDoc(descriptorToPsi: (DeclarationDescriptorWithSource) -> PsiElement?): KDocTag? {
if (kdoc != null) { var psiDeclaration = this
if (this is ConstructorDescriptor) {
// ConstructorDescriptor resolves to the same JetDeclaration // KDoc for primary constructor is located inside of its class KDoc
val constructorSection = kdoc.findSectionByTag(KDocKnownTag.CONSTRUCTOR) if (psiDeclaration is KtPrimaryConstructor) {
if (constructorSection != null) { psiDeclaration = psiDeclaration.getContainingClassOrObject()
return constructorSection }
}
if (psiDeclaration is KtDeclaration) {
val kdoc = psiDeclaration.docComment
if (kdoc != null) {
if (this is KtConstructor<*>) {
// ConstructorDescriptor resolves to the same JetDeclaration
val constructorSection = kdoc.findSectionByTag(KDocKnownTag.CONSTRUCTOR)
if (constructorSection != null) {
return constructorSection
} }
return kdoc.getDefaultSection() }
return kdoc.getDefaultSection()
}
}
if (this is KtParameter) {
val classKDoc = containingClassOrObject?.getChildOfType<KDoc>()
val subjectName = name
if (classKDoc != null && subjectName != null) {
val propertySection =
classKDoc.findSectionByTag(KDocKnownTag.PROPERTY, subjectName)?.takeIf { this.isPropertyParameter() }
?: classKDoc.findDescendantOfType<KDocTag> { it.knownTag == KDocKnownTag.PARAM && it.getSubjectName() == subjectName }
if (propertySection != null) {
return propertySection
} }
} }
} }
if (this is PropertyDescriptor) { if (this is KtProperty) {
val containingClassDescriptor = this.containingDeclaration as? ClassDescriptor val classKDoc = containingClass()?.getChildOfType<KDoc>()
if (containingClassDescriptor != null) { val subjectName = name
val classKDoc = containingClassDescriptor.findKDoc(descriptorToPsi)?.getParentOfType<KDoc>(false) if (classKDoc != null && subjectName != null) {
if (classKDoc != null) { val propertySection = classKDoc.findSectionByTag(KDocKnownTag.PROPERTY, subjectName)
val propertySection = classKDoc.findSectionByTag(KDocKnownTag.PROPERTY, if (propertySection != null) {
getName().asString()) return propertySection
if (propertySection != null) {
return propertySection
}
} }
} }
} }
if (this is CallableDescriptor) { if (this is KtCallableDeclaration) {
for (baseDescriptor in this.overriddenDescriptors) { val descriptor = this.resolveToDescriptorIfAny() as? CallableDescriptor ?: return null
for (baseDescriptor in descriptor.overriddenDescriptors) {
val baseKDoc = baseDescriptor.original.findKDoc(descriptorToPsi) val baseKDoc = baseDescriptor.original.findKDoc(descriptorToPsi)
if (baseKDoc != null) { if (baseKDoc != null) {
return baseKDoc return baseKDoc
@@ -75,5 +98,4 @@ fun DeclarationDescriptor.findKDoc(
} }
return null return null
} }
@@ -17,17 +17,19 @@
package org.jetbrains.kotlin.idea.inspections package org.jetbrains.kotlin.idea.inspections
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter
/** /**
* @returns string description of declaration, like Function ''name'' * @return string description of declaration, like `Function "describe"`
*/ */
fun KtNamedDeclaration.describe(): String? = when (this) { fun KtNamedDeclaration.describe(): String? = when (this) {
is KtClass -> "Class ''$name''" is KtClass -> "Class \"$name\""
is KtObjectDeclaration -> "Object ''$name''" is KtObjectDeclaration -> "Object \"$name\""
is KtNamedFunction -> "Function ''$name''" is KtNamedFunction -> "Function \"$name\""
is KtSecondaryConstructor -> "Constructor" is KtSecondaryConstructor -> "Constructor"
is KtProperty, is KtParameter -> "Property ''$name''" is KtProperty -> "Property \"$name\""
is KtTypeParameter -> "Type parameter ''$name''" is KtParameter -> if (this.isPropertyParameter()) "Property \"$name\"" else "Parameter \"$name\""
is KtTypeAlias -> "Type alias ''$name''" is KtTypeParameter -> "Type parameter \"$name\""
is KtTypeAlias -> "Type alias \"$name\""
else -> null else -> null
} }
@@ -14,7 +14,6 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiElementVisitor
import com.siyeh.ig.psiutils.TestUtils import com.siyeh.ig.psiutils.TestUtils
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
import org.jetbrains.kotlin.descriptors.MemberDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.unblockDocument import org.jetbrains.kotlin.idea.core.unblockDocument
@@ -30,27 +29,29 @@ import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class KDocMissingDocumentationInspection : AbstractKotlinInspection() { class KDocMissingDocumentationInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor = override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor =
namedDeclarationVisitor { element -> namedDeclarationVisitor { element ->
if (TestUtils.isInTestSourceContent(element)) { if (TestUtils.isInTestSourceContent(element)) {
return@namedDeclarationVisitor return@namedDeclarationVisitor
} }
val nameIdentifier = element.nameIdentifier val nameIdentifier = element.nameIdentifier
val descriptor = element.resolveToDescriptorIfAny(BodyResolveMode.FULL) if (nameIdentifier != null) {
as? DeclarationDescriptorWithVisibility if (element.findKDoc { DescriptorToSourceUtilsIde.getAnyDeclaration(element.project, it) } == null) {
as? MemberDescriptor ?: return@namedDeclarationVisitor val descriptor =
if (nameIdentifier != null && descriptor.isEffectivelyPublicApi) { element.resolveToDescriptorIfAny() as? DeclarationDescriptorWithVisibility ?: return@namedDeclarationVisitor
if (descriptor.findKDoc { DescriptorToSourceUtilsIde.getAnyDeclaration(element.project, it) } == null) { if (descriptor.isEffectivelyPublicApi) {
val message = element.describe()?.let { "$it is missing documentation" } ?: "Missing documentation" val message = element.describe()?.let { "$it is missing documentation" } ?: "Missing documentation"
holder.registerProblem(nameIdentifier, message, AddDocumentationFix()) holder.registerProblem(nameIdentifier, message, AddDocumentationFix())
} }
} }
} }
}
override fun runForWholeFile(): Boolean = true
class AddDocumentationFix : LocalQuickFix { class AddDocumentationFix : LocalQuickFix {
override fun getName(): String = "Add documentation" override fun getName(): String = "Add documentation"
@@ -0,0 +1,6 @@
// PROBLEM: none
/**
* @property a it is A
*/
class A(val <caret>a: A)
@@ -0,0 +1,6 @@
// PROBLEM: none
/**
* @param a is is A
*/
class A(val <caret>a: A)
@@ -1,3 +1,3 @@
// PROBLEM: "Class ''A'' is missing documentation" // PROBLEM: "Class "A" is missing documentation"
class <caret>A class <caret>A
@@ -1,4 +1,4 @@
// PROBLEM: "Class ''A'' is missing documentation" // PROBLEM: "Class "A" is missing documentation"
/** /**
* <caret> * <caret>
+8 -8
View File
@@ -1,6 +1,6 @@
public fun <warning descr="Function ''publicUndocumentedFun'' is missing documentation">publicUndocumentedFun</warning>() {} public fun <warning descr="Function \"publicUndocumentedFun\" is missing documentation">publicUndocumentedFun</warning>() {}
fun <warning descr="Function ''defaultUndocumentedFun'' is missing documentation">defaultUndocumentedFun</warning>() {} fun <warning descr="Function \"defaultUndocumentedFun\" is missing documentation">defaultUndocumentedFun</warning>() {}
/** Some documentation */ /** Some documentation */
public fun publicDocumentedFun() {} public fun publicDocumentedFun() {}
@@ -13,8 +13,8 @@ internal fun internalUndocumentedFun() {}
public class <warning descr="Class ''publicUndocumentedClass'' is missing documentation">publicUndocumentedClass</warning>() {} public class <warning descr="Class \"publicUndocumentedClass\" is missing documentation">publicUndocumentedClass</warning>() {}
class <warning descr="Class ''defaultUndocumentedClass'' is missing documentation">defaultUndocumentedClass</warning>() {} class <warning descr="Class \"defaultUndocumentedClass\" is missing documentation">defaultUndocumentedClass</warning>() {}
/** Some documentation */ /** Some documentation */
public class publicDocumentedClass() {} public class publicDocumentedClass() {}
@@ -66,12 +66,12 @@ private class GrandChildClass : ChildClass() {
override public val internalUndocumentedProperty: Int = 6 override public val internalUndocumentedProperty: Int = 6
} }
open class <warning descr="Class ''SomeClass'' is missing documentation">SomeClass</warning> { open class <warning descr="Class \"SomeClass\" is missing documentation">SomeClass</warning> {
protected fun <warning descr="Function ''testProtected'' is missing documentation">testProtected</warning>() = 1 protected fun <warning descr="Function \"testProtected\" is missing documentation">testProtected</warning>() = 1
} }
class <warning descr="Class ''FinalClassWithProtected'' is missing documentation">FinalClassWithProtected</warning> { class <warning descr="Class \"FinalClassWithProtected\" is missing documentation">FinalClassWithProtected</warning> {
protected fun <warning descr="Function ''testProtected'' is missing documentation">testProtected</warning>() = 1 protected fun <warning descr="Function \"testProtected\" is missing documentation">testProtected</warning>() = 1
} }
private class PrimaryCon(val p: String) private class PrimaryCon(val p: String)
@@ -1590,6 +1590,18 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/kdocMissingDocumentation"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/kdocMissingDocumentation"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
} }
@TestMetadata("primaryConstructorProperty.kt")
public void testPrimaryConstructorProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/kdocMissingDocumentation/primaryConstructorProperty.kt");
doTest(fileName);
}
@TestMetadata("primaryConstructorPropertyAsParam.kt")
public void testPrimaryConstructorPropertyAsParam() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/kdocMissingDocumentation/primaryConstructorPropertyAsParam.kt");
doTest(fileName);
}
@TestMetadata("simple.kt") @TestMetadata("simple.kt")
public void testSimple() throws Exception { public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/kdocMissingDocumentation/simple.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/kdocMissingDocumentation/simple.kt");