Fix Missing documentation inspection for primary ctor properties
#KT-20954 fixed #KT-21005 fixed
This commit is contained in:
@@ -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
-13
@@ -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"
|
||||||
|
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property a it is A
|
||||||
|
*/
|
||||||
|
class A(val <caret>a: A)
|
||||||
Vendored
+6
@@ -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
@@ -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)
|
||||||
|
|||||||
+12
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user