From 470a4bb61505c8f4b0b5567b8b1a20a6dcedb73d Mon Sep 17 00:00:00 2001 From: Simon Ogorodnik Date: Thu, 8 Feb 2018 17:49:06 +0300 Subject: [PATCH] Fix Missing documentation inspection for primary ctor properties #KT-20954 fixed #KT-21005 fixed --- .../jetbrains/kotlin/idea/kdoc/findKDoc.kt | 90 ++++++++++++------- .../idea/inspections/describeDeclaration.kt | 16 ++-- .../KDocMissingDocumentationInspection.kt | 27 +++--- .../primaryConstructorProperty.kt | 6 ++ .../primaryConstructorPropertyAsParam.kt | 6 ++ .../kdocMissingDocumentation/simple.kt | 2 +- .../kdocMissingDocumentation/simple.kt.after | 2 +- .../kdoc/highlighting/MissingDocumentation.kt | 16 ++-- .../LocalInspectionTestGenerated.java | 12 +++ 9 files changed, 113 insertions(+), 64 deletions(-) create mode 100644 idea/testData/inspectionsLocal/kdocMissingDocumentation/primaryConstructorProperty.kt create mode 100644 idea/testData/inspectionsLocal/kdocMissingDocumentation/primaryConstructorPropertyAsParam.kt diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/kdoc/findKDoc.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/kdoc/findKDoc.kt index 06271f3e31e..47ecc2b465e 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/kdoc/findKDoc.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/kdoc/findKDoc.kt @@ -17,56 +17,79 @@ package org.jetbrains.kotlin.idea.kdoc 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.psi.api.KDoc import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag -import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.psi.KtPrimaryConstructor -import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils fun DeclarationDescriptor.findKDoc( - descriptorToPsi: (DeclarationDescriptorWithSource) -> PsiElement? = { DescriptorToSourceUtils.descriptorToDeclaration(it) } + descriptorToPsi: (DeclarationDescriptorWithSource) -> PsiElement? = { DescriptorToSourceUtils.descriptorToDeclaration(it) } ): KDocTag? { if (this is DeclarationDescriptorWithSource) { - var psiDeclaration = descriptorToPsi(this)?.navigationElement - // KDoc for primary constructor is located inside of its class KDoc - if (psiDeclaration is KtPrimaryConstructor) { - psiDeclaration = psiDeclaration.getContainingClassOrObject() - } + val psiDeclaration = descriptorToPsi(this)?.navigationElement + return (psiDeclaration as? KtElement)?.findKDoc(descriptorToPsi) + } + return null +} - if (psiDeclaration is KtDeclaration) { - val kdoc = psiDeclaration.docComment - if (kdoc != null) { - if (this is ConstructorDescriptor) { - // ConstructorDescriptor resolves to the same JetDeclaration - val constructorSection = kdoc.findSectionByTag(KDocKnownTag.CONSTRUCTOR) - if (constructorSection != null) { - return constructorSection - } + +fun KtElement.findKDoc(descriptorToPsi: (DeclarationDescriptorWithSource) -> PsiElement?): KDocTag? { + var psiDeclaration = this + + // KDoc for primary constructor is located inside of its class KDoc + if (psiDeclaration is KtPrimaryConstructor) { + psiDeclaration = psiDeclaration.getContainingClassOrObject() + } + + 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() + val subjectName = name + if (classKDoc != null && subjectName != null) { + val propertySection = + classKDoc.findSectionByTag(KDocKnownTag.PROPERTY, subjectName)?.takeIf { this.isPropertyParameter() } + ?: classKDoc.findDescendantOfType { it.knownTag == KDocKnownTag.PARAM && it.getSubjectName() == subjectName } + if (propertySection != null) { + return propertySection } } } - if (this is PropertyDescriptor) { - val containingClassDescriptor = this.containingDeclaration as? ClassDescriptor - if (containingClassDescriptor != null) { - val classKDoc = containingClassDescriptor.findKDoc(descriptorToPsi)?.getParentOfType(false) - if (classKDoc != null) { - val propertySection = classKDoc.findSectionByTag(KDocKnownTag.PROPERTY, - getName().asString()) - if (propertySection != null) { - return propertySection - } + if (this is KtProperty) { + val classKDoc = containingClass()?.getChildOfType() + val subjectName = name + if (classKDoc != null && subjectName != null) { + val propertySection = classKDoc.findSectionByTag(KDocKnownTag.PROPERTY, subjectName) + if (propertySection != null) { + return propertySection } } } - if (this is CallableDescriptor) { - for (baseDescriptor in this.overriddenDescriptors) { + if (this is KtCallableDeclaration) { + val descriptor = this.resolveToDescriptorIfAny() as? CallableDescriptor ?: return null + + for (baseDescriptor in descriptor.overriddenDescriptors) { val baseKDoc = baseDescriptor.original.findKDoc(descriptorToPsi) if (baseKDoc != null) { return baseKDoc @@ -75,5 +98,4 @@ fun DeclarationDescriptor.findKDoc( } return null -} - +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/describeDeclaration.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/describeDeclaration.kt index 016503d99df..ebef4c75d40 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/describeDeclaration.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/describeDeclaration.kt @@ -17,17 +17,19 @@ package org.jetbrains.kotlin.idea.inspections 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) { - is KtClass -> "Class ''$name''" - is KtObjectDeclaration -> "Object ''$name''" - is KtNamedFunction -> "Function ''$name''" + is KtClass -> "Class \"$name\"" + is KtObjectDeclaration -> "Object \"$name\"" + is KtNamedFunction -> "Function \"$name\"" is KtSecondaryConstructor -> "Constructor" - is KtProperty, is KtParameter -> "Property ''$name''" - is KtTypeParameter -> "Type parameter ''$name''" - is KtTypeAlias -> "Type alias ''$name''" + is KtProperty -> "Property \"$name\"" + is KtParameter -> if (this.isPropertyParameter()) "Property \"$name\"" else "Parameter \"$name\"" + is KtTypeParameter -> "Type parameter \"$name\"" + is KtTypeAlias -> "Type alias \"$name\"" else -> null } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/kdoc/KDocMissingDocumentationInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/kdoc/KDocMissingDocumentationInspection.kt index c34997c4f44..a7f7450a263 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/kdoc/KDocMissingDocumentationInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/kdoc/KDocMissingDocumentationInspection.kt @@ -14,7 +14,6 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiElementVisitor import com.siyeh.ig.psiutils.TestUtils 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.codeInsight.DescriptorToSourceUtilsIde 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.getParentOfType import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi -import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode class KDocMissingDocumentationInspection : AbstractKotlinInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor = - namedDeclarationVisitor { element -> - if (TestUtils.isInTestSourceContent(element)) { - return@namedDeclarationVisitor - } - val nameIdentifier = element.nameIdentifier - val descriptor = element.resolveToDescriptorIfAny(BodyResolveMode.FULL) - as? DeclarationDescriptorWithVisibility - as? MemberDescriptor ?: return@namedDeclarationVisitor - if (nameIdentifier != null && descriptor.isEffectivelyPublicApi) { - if (descriptor.findKDoc { DescriptorToSourceUtilsIde.getAnyDeclaration(element.project, it) } == null) { + namedDeclarationVisitor { element -> + if (TestUtils.isInTestSourceContent(element)) { + return@namedDeclarationVisitor + } + val nameIdentifier = element.nameIdentifier + if (nameIdentifier != null) { + if (element.findKDoc { DescriptorToSourceUtilsIde.getAnyDeclaration(element.project, it) } == null) { + val descriptor = + element.resolveToDescriptorIfAny() as? DeclarationDescriptorWithVisibility ?: return@namedDeclarationVisitor + if (descriptor.isEffectivelyPublicApi) { val message = element.describe()?.let { "$it is missing documentation" } ?: "Missing documentation" holder.registerProblem(nameIdentifier, message, AddDocumentationFix()) } } - } + } + + override fun runForWholeFile(): Boolean = true + class AddDocumentationFix : LocalQuickFix { override fun getName(): String = "Add documentation" diff --git a/idea/testData/inspectionsLocal/kdocMissingDocumentation/primaryConstructorProperty.kt b/idea/testData/inspectionsLocal/kdocMissingDocumentation/primaryConstructorProperty.kt new file mode 100644 index 00000000000..ede2952e420 --- /dev/null +++ b/idea/testData/inspectionsLocal/kdocMissingDocumentation/primaryConstructorProperty.kt @@ -0,0 +1,6 @@ +// PROBLEM: none + +/** + * @property a it is A + */ +class A(val a: A) \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/kdocMissingDocumentation/primaryConstructorPropertyAsParam.kt b/idea/testData/inspectionsLocal/kdocMissingDocumentation/primaryConstructorPropertyAsParam.kt new file mode 100644 index 00000000000..90eb09c8279 --- /dev/null +++ b/idea/testData/inspectionsLocal/kdocMissingDocumentation/primaryConstructorPropertyAsParam.kt @@ -0,0 +1,6 @@ +// PROBLEM: none + +/** + * @param a is is A + */ +class A(val a: A) \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/kdocMissingDocumentation/simple.kt b/idea/testData/inspectionsLocal/kdocMissingDocumentation/simple.kt index 11bc1eaa747..20a43dd6a47 100644 --- a/idea/testData/inspectionsLocal/kdocMissingDocumentation/simple.kt +++ b/idea/testData/inspectionsLocal/kdocMissingDocumentation/simple.kt @@ -1,3 +1,3 @@ -// PROBLEM: "Class ''A'' is missing documentation" +// PROBLEM: "Class "A" is missing documentation" class A \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/kdocMissingDocumentation/simple.kt.after b/idea/testData/inspectionsLocal/kdocMissingDocumentation/simple.kt.after index 115dddeb93c..7097e0e3562 100644 --- a/idea/testData/inspectionsLocal/kdocMissingDocumentation/simple.kt.after +++ b/idea/testData/inspectionsLocal/kdocMissingDocumentation/simple.kt.after @@ -1,4 +1,4 @@ -// PROBLEM: "Class ''A'' is missing documentation" +// PROBLEM: "Class "A" is missing documentation" /** * diff --git a/idea/testData/kdoc/highlighting/MissingDocumentation.kt b/idea/testData/kdoc/highlighting/MissingDocumentation.kt index 34d652209a1..46ae3fc1d15 100644 --- a/idea/testData/kdoc/highlighting/MissingDocumentation.kt +++ b/idea/testData/kdoc/highlighting/MissingDocumentation.kt @@ -1,6 +1,6 @@ -public fun publicUndocumentedFun() {} -fun defaultUndocumentedFun() {} +public fun publicUndocumentedFun() {} +fun defaultUndocumentedFun() {} /** Some documentation */ public fun publicDocumentedFun() {} @@ -13,8 +13,8 @@ internal fun internalUndocumentedFun() {} -public class publicUndocumentedClass() {} -class defaultUndocumentedClass() {} +public class publicUndocumentedClass() {} +class defaultUndocumentedClass() {} /** Some documentation */ public class publicDocumentedClass() {} @@ -66,12 +66,12 @@ private class GrandChildClass : ChildClass() { override public val internalUndocumentedProperty: Int = 6 } -open class SomeClass { - protected fun testProtected() = 1 +open class SomeClass { + protected fun testProtected() = 1 } -class FinalClassWithProtected { - protected fun testProtected() = 1 +class FinalClassWithProtected { + protected fun testProtected() = 1 } private class PrimaryCon(val p: String) diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 3893ff7335a..010fbedfc5e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -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); } + @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") public void testSimple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/kdocMissingDocumentation/simple.kt");