diff --git a/diktat-analysis.yml b/diktat-analysis.yml index d6bc4fe00a..2184ee1949 100644 --- a/diktat-analysis.yml +++ b/diktat-analysis.yml @@ -312,5 +312,9 @@ maxNestedBlockQuantity: '4' # Checks that function use default values, instead overloading - name: WRONG_OVERLOADING_FUNCTION_ARGUMENTS + enabled: true + configuration: {} +# Checks that property in constructor doesn't contains comment +- name: KDOC_NO_CONSTRUCTOR_PROPERTY enabled: true configuration: {} \ No newline at end of file diff --git a/diktat-rules/src/main/kotlin/generated/WarningNames.kt b/diktat-rules/src/main/kotlin/generated/WarningNames.kt index 4a69fb0823..478ec26b92 100644 --- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt +++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt @@ -77,6 +77,8 @@ object WarningNames { const val KDOC_NO_DEPRECATED_TAG: String = "KDOC_NO_DEPRECATED_TAG" + const val KDOC_NO_CONSTRUCTOR_PROPERTY: String = "KDOC_NO_CONSTRUCTOR_PROPERTY" + const val HEADER_WRONG_FORMAT: String = "HEADER_WRONG_FORMAT" const val HEADER_MISSING_OR_WRONG_COPYRIGHT: String = "HEADER_MISSING_OR_WRONG_COPYRIGHT" diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt index 532c80f3ef..aaf22756e7 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt @@ -53,6 +53,7 @@ enum class Warnings(private val canBeAutoCorrected: Boolean, private val warn: S KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS(true, "in KDoc there should be exactly one empty line after special tags"), KDOC_NO_EMPTY_TAGS(false, "no empty descriptions in tag blocks are allowed"), KDOC_NO_DEPRECATED_TAG(true, "KDoc doesn't support @deprecated tag, use @Deprecated annotation instead"), + KDOC_NO_CONSTRUCTOR_PROPERTY(true, "replace comment before property with @property tag in KDoc"), HEADER_WRONG_FORMAT(true, "file header comments should be properly formatted"), HEADER_MISSING_OR_WRONG_COPYRIGHT(true, "file header comment must include copyright information inside a block comment"), WRONG_COPYRIGHT_YEAR(true, "year defined in copyright and current year are different"), diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocComments.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocComments.kt index 6bd512600c..18e6d45d52 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocComments.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocComments.kt @@ -1,29 +1,46 @@ package org.cqfn.diktat.ruleset.rules.kdoc -import com.pinterest.ktlint.core.KtLint.FILE_PATH_USER_DATA_KEY import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT import com.pinterest.ktlint.core.ast.ElementType.CLASS import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY +import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT import com.pinterest.ktlint.core.ast.ElementType.FILE import com.pinterest.ktlint.core.ast.ElementType.FUN +import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER import com.pinterest.ktlint.core.ast.ElementType.KDOC +import com.pinterest.ktlint.core.ast.ElementType.KDOC_END +import com.pinterest.ktlint.core.ast.ElementType.KDOC_TEXT import com.pinterest.ktlint.core.ast.ElementType.MODIFIER_LIST +import com.pinterest.ktlint.core.ast.ElementType.PRIMARY_CONSTRUCTOR import com.pinterest.ktlint.core.ast.ElementType.PROPERTY +import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER +import com.pinterest.ktlint.core.ast.ElementType.VAL_KEYWORD +import com.pinterest.ktlint.core.ast.ElementType.VAR_KEYWORD +import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE +import com.pinterest.ktlint.core.ast.parent import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.getCommonConfiguration import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_CONSTRUCTOR_PROPERTY import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_CLASS_ELEMENTS import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_TOP_LEVEL import org.cqfn.diktat.ruleset.utils.* import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet +import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag +import org.jetbrains.kotlin.psi.psiUtil.parents /** * This rule checks the following features in KDocs: * 1) All top-level (file level) functions and classes with public or internal access should have KDoc * 2) All internal elements in class like class, property or function should be documented with KDoc + * 3) All properties declared in the primary constructor are documented using `@property` tag in class KDoc */ class KdocComments(private val configRules: List) : Rule("kdoc-comments") { + companion object { private val statementsToDocument = TokenSet.create(CLASS, FUN, PROPERTY) } @@ -45,7 +62,148 @@ class KdocComments(private val configRules: List) : Rule("kdoc-comm when (node.elementType) { FILE -> checkTopLevelDoc(node) CLASS -> checkClassElements(node) + VALUE_PARAMETER -> checkValueParameter(node) + } + } + + @Suppress("UnsafeCallOnNullableType") + private fun checkValueParameter(node: ASTNode) { + if (node.parents().none { it.elementType == PRIMARY_CONSTRUCTOR } || + !(node.hasChildOfType(VAL_KEYWORD) || node.hasChildOfType(VAR_KEYWORD))) return + val prevComment = if (node.treePrev.elementType == WHITE_SPACE && + (node.treePrev.treePrev.elementType == EOL_COMMENT || + node.treePrev.treePrev.elementType == BLOCK_COMMENT)) { + node.treePrev.treePrev + } else if (node.hasChildOfType(KDOC)) { + node.findChildByType(KDOC)!! + } else if (node.treePrev.elementType == BLOCK_COMMENT) { + node.treePrev + } else { + null + } + val kDocBeforeClass = node.parent({ it.elementType == CLASS })!!.findChildByType(KDOC) + if (prevComment != null) { + if (kDocBeforeClass != null) + checkKDocBeforeClass(node, kDocBeforeClass, prevComment) + else + createKDocWithProperty(node, prevComment) + } else { + if (kDocBeforeClass != null) + checkBasicKDocBeforeClass(node, kDocBeforeClass) + else + createKDocBasicKDoc(node) + } + } + + @Suppress("UnsafeCallOnNullableType") + private fun checkBasicKDocBeforeClass(node: ASTNode, kDocBeforeClass: ASTNode) { + val propertyInClassKDoc = kDocBeforeClass + .kDocTags() + ?.firstOrNull { it.knownTag == KDocKnownTag.PROPERTY && it.getSubjectName() == node.findChildByType(IDENTIFIER)!!.text } + if (propertyInClassKDoc == null && node.getFirstChildWithType(MODIFIER_LIST).isAccessibleOutside()) { + KDOC_NO_CONSTRUCTOR_PROPERTY.warnAndFix(configRules, emitWarn, isFixMode, + "add <${node.findChildByType(IDENTIFIER)!!.text}> to KDoc", node.startOffset, node) { + insertTextInKDoc(kDocBeforeClass, " * @property ${node.findChildByType(IDENTIFIER)!!.text}\n") } + } + } + + @Suppress("UnsafeCallOnNullableType") + private fun checkKDocBeforeClass(node: ASTNode, kDocBeforeClass: ASTNode, prevComment: ASTNode) { + val propertyInClassKDoc = kDocBeforeClass + .kDocTags() + ?.firstOrNull { it.knownTag == KDocKnownTag.PROPERTY && it.getSubjectName() == node.findChildByType(IDENTIFIER)!!.text } + ?.node + val propertyInLocalKDoc = if (prevComment.elementType == KDOC) + prevComment + .kDocTags() + ?.firstOrNull { it.knownTag == KDocKnownTag.PROPERTY && it.getSubjectName() == node.findChildByType(IDENTIFIER)!!.text } + ?.node + else + null + if (prevComment.elementType == KDOC || prevComment.elementType == BLOCK_COMMENT) { + handleKDcoAndBlock(node, prevComment, kDocBeforeClass, propertyInClassKDoc, propertyInLocalKDoc) + } else { + KDOC_NO_CONSTRUCTOR_PROPERTY.warnAndFix(configRules, emitWarn, isFixMode, node.findChildByType(IDENTIFIER)!!.text, prevComment.startOffset, node) { + handleCommentBefore(node, kDocBeforeClass, prevComment, propertyInClassKDoc) + } + } + } + + @Suppress("UnsafeCallOnNullableType") + private fun createKDocBasicKDoc(node: ASTNode) { + KDOC_NO_CONSTRUCTOR_PROPERTY.warnAndFix(configRules, emitWarn, isFixMode, + "add <${node.findChildByType(IDENTIFIER)!!.text}> to KDoc", node.startOffset, node) { + val newKDoc = KotlinParser().createNode("/**\n * @property ${node.findChildByType(IDENTIFIER)!!.text}\n */") + val classNode = node.parent({ it.elementType == CLASS })!! + classNode.addChild(PsiWhiteSpaceImpl("\n"), classNode.firstChildNode) + classNode.addChild(newKDoc.findChildByType(KDOC)!!, classNode.firstChildNode) + } + } + + @Suppress("UnsafeCallOnNullableType") + private fun createKDocWithProperty(node: ASTNode, prevComment: ASTNode) { + KDOC_NO_CONSTRUCTOR_PROPERTY.warnAndFix(configRules, emitWarn, isFixMode, prevComment.text, prevComment.startOffset, node) { + val classNode = node.parent({ it.elementType == CLASS })!! + val newKDocText = if (prevComment.elementType == KDOC) prevComment.text else if (prevComment.elementType == EOL_COMMENT) { + "/**\n * @property ${node.findChildByType(IDENTIFIER)!!.text} ${prevComment.text.removePrefix("//")}\n */" + } else { + "/**\n * @property ${node.findChildByType(IDENTIFIER)!!.text}${prevComment.text.removePrefix("/*").removeSuffix("*/")} */" + } + val newKDoc = KotlinParser().createNode(newKDocText).findChildByType(KDOC)!! + classNode.addChild(PsiWhiteSpaceImpl("\n"), classNode.firstChildNode) + classNode.addChild(newKDoc, classNode.firstChildNode) + if (prevComment.elementType == EOL_COMMENT) { + node.treeParent.removeRange(prevComment, node) + } else { + if (prevComment.treeNext.elementType == WHITE_SPACE) + node.removeChild(prevComment.treeNext) + node.removeChild(prevComment) + } + } + } + + @Suppress("UnsafeCallOnNullableType") + private fun handleKDcoAndBlock(node: ASTNode, prevComment: ASTNode, kDocBeforeClass: ASTNode, propertyInClassKDoc: ASTNode?, propertyInLocalKDoc: ASTNode?) { + val kDocText = if (prevComment.elementType == KDOC) + prevComment.text.removePrefix("/**").removeSuffix("*/") + else + prevComment.text.removePrefix("/*").removeSuffix("*/") + val isFixable = (propertyInClassKDoc != null && propertyInLocalKDoc != null) || + (propertyInClassKDoc == null && propertyInLocalKDoc == null && kDocText.replace("\n+".toRegex(), "").lines().size != 1) + KDOC_NO_CONSTRUCTOR_PROPERTY.warnAndFix(configRules, emitWarn, !isFixable, prevComment.text, prevComment.startOffset, node, !isFixable) { + if (propertyInClassKDoc == null && propertyInLocalKDoc == null) + insertTextInKDoc(kDocBeforeClass, " * @property ${node.findChildByType(IDENTIFIER)!!.text} ${kDocText.replace("\n+".toRegex(), "").removePrefix("*")}") + else + insertTextInKDoc(kDocBeforeClass, "${kDocText.trim()}\n") + + if (prevComment.treeNext.elementType == WHITE_SPACE) + node.removeChild(prevComment.treeNext) + node.removeChild(prevComment) + } + } + + @Suppress("UnsafeCallOnNullableType") + private fun handleCommentBefore(node: ASTNode, kDocBeforeClass: ASTNode, prevComment: ASTNode, propertyInClassKDoc: ASTNode?) { + if (propertyInClassKDoc != null) { + if (propertyInClassKDoc.hasChildOfType(KDOC_TEXT)) { + val kdocText = propertyInClassKDoc.findChildByType(KDOC_TEXT)!! + (kdocText as LeafPsiElement).replaceWithText("${kdocText.text} ${prevComment.text}") + } else { + propertyInClassKDoc.addChild(LeafPsiElement(KDOC_TEXT, prevComment.text), null) + } + } else { + insertTextInKDoc(kDocBeforeClass, "* @property ${node.findChildByType(IDENTIFIER)!!.text} ${prevComment.text.removeRange(0, 2)}\n") + } + node.treeParent.removeRange(prevComment, node) + } + + @Suppress("UnsafeCallOnNullableType") + private fun insertTextInKDoc(kDocBeforeClass: ASTNode, insertText: String) { + val allKDocText = kDocBeforeClass.text + val endKDoc = kDocBeforeClass.findChildByType(KDOC_END)!!.text + val newKDocText = StringBuilder(allKDocText).insert(allKDocText.indexOf(endKDoc), insertText).toString() + kDocBeforeClass.treeParent.replaceChild(kDocBeforeClass, KotlinParser().createNode(newKDocText).findChildByType(KDOC)!!) } private fun checkClassElements(node: ASTNode) { @@ -77,7 +235,7 @@ class KdocComments(private val configRules: List) : Rule("kdoc-comm val name = node.getIdentifierName() if (modifier.isAccessibleOutside() && kdoc == null) { - warning.warn(configRules, emitWarn, isFixMode, name!!.text, node.startOffset,node) + warning.warn(configRules, emitWarn, isFixMode, name!!.text, node.startOffset, node) } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KDocUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KDocUtils.kt index d72d927aa7..9290c41dbc 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KDocUtils.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KDocUtils.kt @@ -10,11 +10,15 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag +import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty -fun ASTNode.kDocTags(): Collection? { +fun ASTNode.kDocTags(): List? { require(this.elementType == ElementType.KDOC) { "kDoc tags can be retrieved only from KDOC node" } - return this.getFirstChildWithType(KDOC_SECTION) - ?.getAllChildrenWithType(ElementType.KDOC_TAG)?.map { it.psi as KDocTag } + return this.getAllChildrenWithType(KDOC_SECTION).flatMap { + sectionNode -> + sectionNode.getAllChildrenWithType(ElementType.KDOC_TAG) + .map { its -> its.psi as KDocTag } + } } fun Iterable.hasKnownKDocTag(knownTag: KDocKnownTag): Boolean = diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml index 39c4644155..5c7e89d7fe 100644 --- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml +++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml @@ -310,5 +310,9 @@ maxNestedBlockQuantity: '4' # Checks that function use default values, instead overloading - name: WRONG_OVERLOADING_FUNCTION_ARGUMENTS + enabled: true + configuration: {} +# Checks that property in constructor doesn't contains comment +- name: KDOC_NO_CONSTRUCTOR_PROPERTY enabled: true configuration: {} \ No newline at end of file diff --git a/diktat-rules/src/main/resources/diktat-analysis.yml b/diktat-rules/src/main/resources/diktat-analysis.yml index 9cdae7eb74..98a236b47c 100644 --- a/diktat-rules/src/main/resources/diktat-analysis.yml +++ b/diktat-rules/src/main/resources/diktat-analysis.yml @@ -313,5 +313,9 @@ maxNestedBlockQuantity: '4' # Checks that function use default values, instead overloading - name: WRONG_OVERLOADING_FUNCTION_ARGUMENTS + enabled: true + configuration: {} +# Checks that property in constructor doesn't contains comment +- name: KDOC_NO_CONSTRUCTOR_PROPERTY enabled: true configuration: {} \ No newline at end of file diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocCommentsFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocCommentsFixTest.kt new file mode 100644 index 0000000000..9a8ebd2f34 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocCommentsFixTest.kt @@ -0,0 +1,23 @@ +package org.cqfn.diktat.ruleset.chapter2 + +import generated.WarningNames +import org.cqfn.diktat.ruleset.rules.kdoc.KdocComments +import org.cqfn.diktat.util.FixTestBase +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class KdocCommentsFixTest: FixTestBase("test/paragraph2/kdoc/", ::KdocComments) { + + + @Test + @Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY) + fun `check fix with class kdoc`() { + fixAndCompare("ConstructorCommentExpected.kt", "ConstructorCommentTest.kt") + } + + @Test + @Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY) + fun `check fix without class kdoc`() { + fixAndCompare("ConstructorCommentNoKDocExpected.kt", "ConstructorCommentNoKDocTest.kt") + } +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocWarnTest.kt index 66be790474..8bf5e1bad6 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocWarnTest.kt @@ -8,6 +8,7 @@ import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID import org.cqfn.diktat.ruleset.rules.kdoc.KdocComments import org.cqfn.diktat.util.LintTestBase import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Tags class KdocWarnTest : LintTestBase(::KdocComments) { private val ruleId: String = "$DIKTAT_RULE_SET_ID:kdoc-comments" @@ -203,4 +204,198 @@ class KdocWarnTest : LintTestBase(::KdocComments) { """.trimMargin() ) } + + @Test + @Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY) + fun `check simple primary constructor with comment`() { + lintMethod( + """ + |/** + | * @property name d + | * @param adsf + | * @return something + | */ + |class Example constructor ( + | // short + | val name: String + |) { + |} + """.trimMargin(), + LintError(7, 4, ruleId, "${KDOC_NO_CONSTRUCTOR_PROPERTY.warnText()} name", true) + ) + } + + @Test + @Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY) + fun `shouldn't trigger because not primary constructor`() { + lintMethod( + """ + |/** + | * @property name d + | * @property anotherName text + | */ + |class Example { + | constructor( + | // name + | name: String, + | anotherName: String, + | OneMoreName: String + | ) + |} + """.trimMargin() + ) + } + + @Test + @Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY) + fun `check constructor with comment`() { + lintMethod( + """ + |/** + | * @return some + | */ + |class Example ( + | //some descriptions + | val name: String, + | anotherName: String, + | OneMoreName: String + | ) { + |} + """.trimMargin(), + LintError(5, 4, ruleId, "${KDOC_NO_CONSTRUCTOR_PROPERTY.warnText()} name", true) + ) + } + + @Test + @Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY) + fun `check constructor with block comment`() { + lintMethod( + """ + |/** + | * @return some + | */ + |class Example ( + | /*some descriptions*/val name: String, + | anotherName: String, + | OneMoreName: String + | ) { + |} + """.trimMargin(), + LintError(5, 4, ruleId, "${KDOC_NO_CONSTRUCTOR_PROPERTY.warnText()} /*some descriptions*/", true) + ) + } + + @Test + @Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY) + fun `check not property`() { + lintMethod( + """ + |/** + | * @return some + | */ + |class Example ( + | //some descriptions + | name: String, + | anotherName: String, + | OneMoreName: String + | ) { + |} + """.trimMargin() + ) + } + + @Test + @Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY) + fun `check constructor with kdoc`() { + lintMethod( + """ + |/** + | * @return some + | */ + |class Example ( + | /** + | * some descriptions + | * @return fdv + | */ + | + | val name: String, + | anotherName: String, + | OneMoreName: String + | ) { + |} + """.trimMargin(), + LintError(5, 4, ruleId, "${KDOC_NO_CONSTRUCTOR_PROPERTY.warnText()} /**...", true) + ) + } + + @Test + @Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY) + fun `shouldn't fix`() { + lintMethod( + """ + |/** + | * @property name text + | */ + |class Example ( + | /** + | * sdcjkh + | * @property name text2 + | */ + | val name: String, + | ) { + |} + """.trimMargin(), + LintError(5, 4, ruleId, "${KDOC_NO_CONSTRUCTOR_PROPERTY.warnText()} /**...", false) + ) + } + + @Test + @Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY) + fun `shouldn't trigger`() { + lintMethod( + """ + |/** + | * text + | */ + |class Example ( + | private val name: String, + | ) { + |} + """.trimMargin() + ) + } + + @Test + @Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY) + fun `no property kdoc`() { + lintMethod( + """ + |/** + | * @property Name text + | */ + |class Example ( + | val name: String, + | ) { + |} + """.trimMargin(), + LintError(5, 4, ruleId, "${KDOC_NO_CONSTRUCTOR_PROPERTY.warnText()} add to KDoc", true) + ) + } + + @Test + @Tags(Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY), Tag(WarningNames.MISSING_KDOC_TOP_LEVEL)) + fun `no property kdoc and class`() { + lintMethod( + """ + |class Example ( + | val name: String, + | private val surname: String + | ) { + |} + """.trimMargin(), + LintError(1, 1, ruleId, "${MISSING_KDOC_TOP_LEVEL.warnText()} Example"), + LintError(2, 4, ruleId, "${KDOC_NO_CONSTRUCTOR_PROPERTY.warnText()} add to KDoc", true), + LintError(3, 4, ruleId, "${KDOC_NO_CONSTRUCTOR_PROPERTY.warnText()} add to KDoc", true) + ) + } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt index ff080e5a4b..2caae4f208 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt @@ -57,8 +57,7 @@ class DiktatSmokeTest : FixTestBase("test/smoke/src/main/kotlin", fixAndCompare("Example2Expected.kt", "Example2Test.kt") unfixedLintErrors.assertEquals( LintError(1, 1, "$DIKTAT_RULE_SET_ID:file-naming", "${FILE_NAME_INCORRECT.warnText()} Example2Test.kt_copy", true), // todo this is a false one - LintError(1, 1, "$DIKTAT_RULE_SET_ID:header-comment", "${HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE.warnText()} $expectedFileAbsolutePath", false), - LintError(3, 64, "$DIKTAT_RULE_SET_ID:kdoc-comments", "${MISSING_KDOC_TOP_LEVEL.warnText()} Example", false) + LintError(1, 1, "$DIKTAT_RULE_SET_ID:header-comment", "${HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE.warnText()} $expectedFileAbsolutePath", false) ) } } diff --git a/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentExpected.kt b/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentExpected.kt new file mode 100644 index 0000000000..c528ce2eb3 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentExpected.kt @@ -0,0 +1,27 @@ +package test.paragraph2.kdoc + +/** + * @property name another text + * some text +*/ +class A ( + var name: String +){} + +/** + * @property name another text + */ +class A ( + /** + * @property name some text + */ + val name: String +){} + +/** + * @property name another text + * text +*/ +class A ( + val name: String +){} diff --git a/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentNoKDocExpected.kt b/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentNoKDocExpected.kt new file mode 100644 index 0000000000..b9e7799a4f --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentNoKDocExpected.kt @@ -0,0 +1,39 @@ +package test.paragraph2.kdoc + +/** + * @property name comment + */ +class A constructor( + val name: String +){} + +/** + * hello + */ +class A constructor( + val name: String +){} + +/** + * text + */ +class A constructor( + @param:JsonProperty("shortName") private val shortName: String +){} + +/** + * @property name + * @property lastName +*/ +class A constructor( + private var name: String, + val lastName: String +){} + +/** + * @property name + * hello + */ +class A constructor( + val name: String +){} diff --git a/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentNoKDocTest.kt b/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentNoKDocTest.kt new file mode 100644 index 0000000000..09899c53c7 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentNoKDocTest.kt @@ -0,0 +1,32 @@ +package test.paragraph2.kdoc + +class A constructor( + //comment + val name: String +){} + +class A constructor( + /** + * hello + */ + val name: String +){} + +class A constructor( + /** + * text + */ + @param:JsonProperty("shortName") private val shortName: String +){} + +class A constructor( + private var name: String, + val lastName: String +){} + +class A constructor( + /* + * hello + */ + val name: String +){} diff --git a/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentTest.kt b/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentTest.kt new file mode 100644 index 0000000000..562189c413 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentTest.kt @@ -0,0 +1,31 @@ +package test.paragraph2.kdoc + +/** + * @property name another text + */ +class A ( + /** + * some text + */ + var name: String +){} + +/** + * @property name another text + */ +class A ( + /** + * @property name some text + */ + val name: String +){} + +/** + * @property name another text + */ +class A ( + /* + * text + */ + val name: String +){} diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt index 77ccca60e8..d3dc99dc08 100644 --- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt @@ -4,6 +4,10 @@ package your.name.here +/** + * @property foo + * @property bar + */ @ExperimentalStdlibApi public data class Example(val foo: Int, val bar: Double) : SuperExample("lorem ipsum") private class TestException : Exception() diff --git a/info/available-rules.md b/info/available-rules.md index 246381ba22..9c1f022e2e 100644 --- a/info/available-rules.md +++ b/info/available-rules.md @@ -36,6 +36,7 @@ | 2 | 2.3 | KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS | Check: warns if special tags `@apiNote`, `@implNote`, `@implSpec` don't have exactly one empty line after
Fix: removes redundant lines or adds one | yes | |Handle empty lines without leading asterisk| | 2 | 2.3 | KDOC_NO_EMPTY_TAGS | Check: warns if KDoc tags have empty content | no | || | 2 | 2.3 | KDOC_NO_DEPRECATED_TAG | Check: warns if `@deprecated` is used in KDoc
Fix: adds `@Deprecated` annotation with message, removes tag | yes | |Annotation's `replaceWith` field can be filled too| +| 2 | 2.3 | KDOC_NO_CONSTRUCTOR_PROPERTY | Check: warns if there is comment before property in constructor | yes | -| | 2 | 2.5 | KDOC_TRIVIAL_KDOC_ON_FUNCTION | Check: warns if KDoc contains single line with words 'return', 'get' or 'set' | no | - | | 2 | 2.4 | HEADER_WRONG_FORMAT | Checks: warns if there is no newline after header KDoc
Fix: adds newline | yes | |Check if header is on the very top of file. It's hard to determine when it's not.| | 2 | 2.4 | HEADER_MISSING_OR_WRONG_COPYRIGHT | Checks: copyright exists on top of file and is properly formatted (as a block comment)
Fix: adds copyright if it is missing and required | yes |mandatoryCopyright|Allow setting copyright patterns via configuration|