From ce7e8102c5671f3110142e4409bc0355f5164d73 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Wed, 17 Jun 2020 20:01:26 +0800 Subject: [PATCH] Initial commit with KDoc formatting checks ### What's done: * Added check for number of white spaces after tags in KDocs * Updated rules-config.json --- .../ruleset/huawei/constants/Warnings.kt | 9 +- .../ruleset/huawei/rules/KdocFormatting.kt | 137 +++++++++++++-- .../ruleset/huawei/rules/KdocMethods.kt | 4 +- .../fixbot/ruleset/huawei/utils/KDocUtils.kt | 6 +- .../chapter1/IdentifierNamingFixTest.kt | 27 +-- .../huawei/chapter1/PackageNamingFixTest.kt | 24 +-- .../huawei/chapter1/PackagePathFixTest.kt | 21 +-- .../huawei/chapter2/KdocFormattingFixTest.kt | 43 ++--- .../huawei/chapter2/KdocFormattingTest.kt | 160 ++++++++++++++++++ .../huawei/chapter2/KdocSignatureTest.kt | 25 +-- .../fixbot/ruleset/huawei/utils/TestUtils.kt | 30 ++++ .../paragraph2/kdoc/OrderedTagsExpected.kt | 12 ++ .../test/paragraph2/kdoc/OrderedTagsTest.kt | 12 ++ .../paragraph2/kdoc/SpacesAfterTagExpected.kt | 12 ++ .../paragraph2/kdoc/SpacesAfterTagTest.kt | 12 ++ .../kdoc/SpecialTagsInKdocExpected.kt | 15 ++ .../paragraph2/kdoc/SpecialTagsInKdocTest.kt | 14 ++ .../processing/TestComparatorUnit.kt | 2 +- rules-config.json | 24 ++- 19 files changed, 478 insertions(+), 111 deletions(-) create mode 100644 diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/utils/TestUtils.kt create mode 100644 diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/OrderedTagsExpected.kt create mode 100644 diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/OrderedTagsTest.kt create mode 100644 diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpacesAfterTagExpected.kt create mode 100644 diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpacesAfterTagTest.kt create mode 100644 diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpecialTagsInKdocExpected.kt create mode 100644 diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpecialTagsInKdocTest.kt diff --git a/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/constants/Warnings.kt b/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/constants/Warnings.kt index 8f7cdc5a40..6c3761d276 100644 --- a/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/constants/Warnings.kt +++ b/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/constants/Warnings.kt @@ -40,9 +40,14 @@ enum class Warnings(private val id: Int, private val canBeAutoCorrected: Boolean KDOC_WITHOUT_RETURN_TAG(23, true, "all methods which return values should have @return tag in KDoc"), KDOC_WITHOUT_THROWS_TAG(24, true, "all methods which throw exceptions should have @throws tag in KDoc"), BLANK_LINE_AFTER_KDOC(25, true, "there should be no empty line between Kdoc and code it is describing"), - + KDOC_WRONG_SPACES_AFTER_TAG(26, true, "there should be exactly one white space after tag name in KDoc"), + KDOC_WRONG_TAGS_ORDER(27, true, "in KDoc standard tags are arranged in order @param, @return, @throws, but are"), + KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS(28, true, "in KDoc there should be exactly one empty line after special tags"), + KDOC_NO_EMPTY_TAGS(29, false, "no empty descriptions in tag blocks are allowed"), + KDOC_NO_DEPRECATED_TAG(30, false, "KDoc doesn't support @deprecated tag, use @Deprecated annotation instead"), + // ====== incorrect place and warn number ==== - INCORRECT_PACKAGE_SEPARATOR(26, true, "package name parts should be separated only by dots - there should be no other symbols like underscores (_)") + INCORRECT_PACKAGE_SEPARATOR(31, true, "package name parts should be separated only by dots - there should be no other symbols like underscores (_)") ; override fun ruleName(): String = this.name diff --git a/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/rules/KdocFormatting.kt b/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/rules/KdocFormatting.kt index 283b43081c..b51e6497a9 100644 --- a/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/rules/KdocFormatting.kt +++ b/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/rules/KdocFormatting.kt @@ -1,21 +1,31 @@ package com.huawei.rri.fixbot.ruleset.huawei.rules -import com.huawei.rri.fixbot.ruleset.huawei.constants.Warnings.BLANK_LINE_AFTER_KDOC -import com.huawei.rri.fixbot.ruleset.huawei.utils.countSubStringOccurrences -import com.huawei.rri.fixbot.ruleset.huawei.utils.getFirstChildWithType -import com.huawei.rri.fixbot.ruleset.huawei.utils.leaveOnlyOneNewLine +import com.huawei.rri.fixbot.ruleset.huawei.constants.Warnings.* +import com.huawei.rri.fixbot.ruleset.huawei.utils.* import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.ast.ElementType import com.pinterest.ktlint.core.ast.ElementType.CLASS import com.pinterest.ktlint.core.ast.ElementType.FUN +import com.pinterest.ktlint.core.ast.ElementType.KDOC +import com.pinterest.ktlint.core.ast.ElementType.KDOC_LEADING_ASTERISK +import com.pinterest.ktlint.core.ast.ElementType.KDOC_TAG_NAME import com.pinterest.ktlint.core.ast.ElementType.PROPERTY +import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE +import com.pinterest.ktlint.core.ast.prevSibling import config.rules.RulesConfig import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement +import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag +import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag /** * Formatting visitor for Kdoc: * 1) removing all blank lines between Kdoc and the code it's declaring + * 2) ensuring there are no tags with empty content + * 3) ensuring there is only one white space between tag and it's body + * 4) ensuring tags @apiNote, @implSpec, @implNote have one empty line after their body + * 5) ensuring tags @param, @return, @throws are arranged in this order */ class KdocFormatting : Rule("kdoc-formatting") { @@ -35,12 +45,119 @@ class KdocFormatting : Rule("kdoc-formatting") { val declarationTypes = setOf(CLASS, FUN, PROPERTY) if (declarationTypes.contains(node.elementType)) { - val kdoc = node.getFirstChildWithType(ElementType.KDOC) - val nodeAfterKdoc = kdoc?.treeNext - val name = node.getFirstChildWithType(ElementType.IDENTIFIER) - if (nodeAfterKdoc?.elementType == ElementType.WHITE_SPACE && nodeAfterKdoc.text.countSubStringOccurrences("\n") > 1) { - BLANK_LINE_AFTER_KDOC.warnAndFix(confiRules, emitWarn, isFixMode, name!!.text, nodeAfterKdoc.startOffset) { - nodeAfterKdoc.leaveOnlyOneNewLine() + checkBlankLineAfterKdoc(node) + } + + if (node.elementType == KDOC) { + val kDocTags = node.kDocTags() + + checkNoDeprecatedTag(kDocTags) + checkEmptyTags(kDocTags) + checkSpaceAfterTag(kDocTags) + checkBasicTagsOrder(node, kDocTags) + checkNewLineAfterSpecialTags(node) + } + } + + private fun checkBlankLineAfterKdoc(node: ASTNode) { + val kdoc = node.getFirstChildWithType(KDOC) + val nodeAfterKdoc = kdoc?.treeNext + val name = node.getFirstChildWithType(ElementType.IDENTIFIER) + if (nodeAfterKdoc?.elementType == WHITE_SPACE && nodeAfterKdoc.text.countSubStringOccurrences("\n") > 1) { + BLANK_LINE_AFTER_KDOC.warnAndFix(confiRules, emitWarn, isFixMode, name!!.text, nodeAfterKdoc.startOffset) { + nodeAfterKdoc.leaveOnlyOneNewLine() + } + } + } + + private fun checkNoDeprecatedTag(kDocTags: Collection?) { + kDocTags?.find { it.name == "deprecated" } + ?.let { + // fixme possible fix would be to remove tag and add annotation, but description should be kept + // also annotation supports different fields, which ideally should be properly filled + KDOC_NO_DEPRECATED_TAG.warn(confiRules, emitWarn, isFixMode, it.text, it.node.startOffset) + } + } + + private fun checkEmptyTags(kDocTags: Collection?) { + kDocTags?.filter { + it.getSubjectName() == null && it.getContent().isEmpty() + }?.forEach { + KDOC_NO_EMPTY_TAGS.warn(confiRules, emitWarn, isFixMode, "@${it.name!!}", it.node.startOffset) + } + } + + private fun checkSpaceAfterTag(kDocTags: Collection?) { + kDocTags?.filter { + it.node.findChildAfter(KDOC_TAG_NAME, WHITE_SPACE)?.text != " " + }?.forEach { + KDOC_WRONG_SPACES_AFTER_TAG.warnAndFix(confiRules, emitWarn, isFixMode, + "@${it.name!!}", it.node.startOffset) { + val whitespaceNode = it.node.findChildAfter(KDOC_TAG_NAME, WHITE_SPACE) + if (whitespaceNode != null) { + it.node.replaceChild(whitespaceNode, LeafPsiElement(WHITE_SPACE, " ")) + } + } + } + } + + private fun checkBasicTagsOrder(node: ASTNode, kDocTags: Collection?) { + // basic tags which are present in current KDoc, in proper order + val basicTagsOrdered = listOf(KDocKnownTag.PARAM, KDocKnownTag.RETURN, KDocKnownTag.THROWS) + .filter { basicTag -> kDocTags?.find { it.knownTag == basicTag } != null } + val basicTags = kDocTags?.filter { basicTagsOrdered.contains(it.knownTag) } + val isTagsInCorrectOrder = basicTags?.map { it.knownTag }?.equals(basicTagsOrdered) + + if (kDocTags != null && !isTagsInCorrectOrder!!) { + KDOC_WRONG_TAGS_ORDER.warnAndFix(confiRules, emitWarn, isFixMode, + basicTags.joinToString(", ") { "@${it.name}" }, basicTags.first().node.startOffset) { + val kDocSection = node.getFirstChildWithType(ElementType.KDOC_SECTION)!! + val basicTagChildren = kDocTags + .filter { basicTagsOrdered.contains(it.knownTag) } + .map { it.node } + + basicTagsOrdered.forEachIndexed { index, tag -> + val tagNode = kDocTags.find { it.knownTag == tag }?.node + kDocSection.addChild(tagNode!!.copyElement(), basicTagChildren[index]) + kDocSection.removeChild(basicTagChildren[index]) + } + } + } + } + + private fun checkNewLineAfterSpecialTags(node: ASTNode) { + val specialTagNames = listOf("implSpec", "implNote", "apiNote") + + val presentSpecialTagNodes = node.getFirstChildWithType(ElementType.KDOC_SECTION) + ?.getAllChildrenWithType(ElementType.KDOC_TAG) + ?.filter { (it.psi as KDocTag).name in specialTagNames } + + val poorlyFormattedTagNodes = presentSpecialTagNodes?.filterNot { specialTagNode -> + // empty line with just * followed by white space or end of block + specialTagNode.lastChildNode.elementType == KDOC_LEADING_ASTERISK + && (specialTagNode.treeNext == null || specialTagNode.treeNext.elementType == WHITE_SPACE + && specialTagNode.treeNext.text.count { it == '\n' } == 1) + // and with no empty line before + && specialTagNode.lastChildNode.treePrev.elementType == WHITE_SPACE + && specialTagNode.lastChildNode.treePrev.treePrev.elementType != KDOC_LEADING_ASTERISK + } + + if (presentSpecialTagNodes != null && poorlyFormattedTagNodes!!.isNotEmpty()) { + KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS.warnAndFix(confiRules, emitWarn, isFixMode, + poorlyFormattedTagNodes.joinToString(", ") { "@${(it.psi as KDocTag).name!!}" }, + poorlyFormattedTagNodes.first().startOffset) { + poorlyFormattedTagNodes.forEach { node -> + while (node.lastChildNode.elementType == KDOC_LEADING_ASTERISK + && node.lastChildNode.treePrev.treePrev.elementType == KDOC_LEADING_ASTERISK) { + node.removeChild(node.lastChildNode) // KDOC_LEADING_ASTERISK + node.removeChild(node.lastChildNode) // WHITE_SPACE + } + if (node.lastChildNode.elementType != KDOC_LEADING_ASTERISK) { + val indent = node.prevSibling { it.elementType == WHITE_SPACE } + ?.text?.substringAfter('\n')?.count { it == ' ' } ?: 0 + node.addChild(LeafPsiElement(WHITE_SPACE, "\n${" ".repeat(indent)}"), null) + node.addChild(LeafPsiElement(KDOC_LEADING_ASTERISK, "*"), null) + } } } } diff --git a/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/rules/KdocMethods.kt b/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/rules/KdocMethods.kt index 628727c057..e9eb34142d 100644 --- a/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/rules/KdocMethods.kt +++ b/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/rules/KdocMethods.kt @@ -45,7 +45,7 @@ class KdocMethods : Rule("kdoc-methods") { private fun checkSignatureDescription(node: ASTNode) { val kDoc = node.getFirstChildWithType(KDOC) - val kDocTags = node.kDocTags() + val kDocTags = kDoc?.kDocTags() val missingParameters = getMissingParameters(node, kDocTags) @@ -55,7 +55,7 @@ class KdocMethods : Rule("kdoc-methods") { if (paramCheckFailed) { Warnings.KDOC_WITHOUT_PARAM_TAG.warnAndFix(confiRules, emitWarn, isFixMode, missingParameters.joinToString(), node.startOffset) { - // FixMe: add separate fix here if any parametr is missing + // FixMe: add separate fix here if any parameter is missing } } if (returnCheckFailed) { diff --git a/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/utils/KDocUtils.kt b/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/utils/KDocUtils.kt index 240280f014..3ef0118594 100644 --- a/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/utils/KDocUtils.kt +++ b/diktat-huawei-rules/src/main/kotlin/com/huawei/rri/fixbot/ruleset/huawei/utils/KDocUtils.kt @@ -1,13 +1,15 @@ package com.huawei.rri.fixbot.ruleset.huawei.utils import com.pinterest.ktlint.core.ast.ElementType +import org.jetbrains.kotlin.com.google.common.base.Preconditions import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag fun ASTNode.kDocTags(): Collection? { - val kDoc = this.getFirstChildWithType(ElementType.KDOC) - return kDoc?.getFirstChildWithType(ElementType.KDOC_SECTION) + Preconditions.checkArgument(this.elementType == ElementType.KDOC, + "kDoc tags can be retrieved only from KDOC node") + return this.getFirstChildWithType(ElementType.KDOC_SECTION) ?.getAllChildrenWithType(ElementType.KDOC_TAG)?.map { it.psi as KDocTag } } diff --git a/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter1/IdentifierNamingFixTest.kt b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter1/IdentifierNamingFixTest.kt index 38b692efbd..3f7537b7d4 100644 --- a/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter1/IdentifierNamingFixTest.kt +++ b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter1/IdentifierNamingFixTest.kt @@ -1,13 +1,10 @@ package com.huawei.rri.fixbot.ruleset.huawei.chapter1 import com.huawei.rri.fixbot.ruleset.huawei.rules.IdentifierNaming -import com.pinterest.ktlint.core.KtLint -import com.pinterest.ktlint.core.Rule -import com.pinterest.ktlint.core.RuleSet +import com.huawei.rri.fixbot.ruleset.huawei.utils.format +import config.rules.RulesConfig import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import com.huawei.rri.fixbot.ruleset.huawei.rules.PackageNaming -import config.rules.RulesConfig import test_framework.processing.TestComparatorUnit class IdentifierNamingFixTest { @@ -36,20 +33,10 @@ class IdentifierNamingFixTest { } - private fun format(text: String, fileName: String): String = IdentifierNaming().format(text, fileName) - - private fun Rule.format(text: String, fileName: String): String { - return KtLint.format( - KtLint.Params( - text = text, - ruleSets = listOf(RuleSet("huawei-codestyle", this@format)), - fileName = fileName, - rulesConfigList = listOf( - RulesConfig("PACKAGE_NAME_INCORRECT", false, ""), - RulesConfig("PACKAGE_NAME_INCORRECT_PREFIX", false, "") - ), - cb = { _, _ -> } - ) + private fun format(text: String, fileName: String): String = IdentifierNaming().format(text, fileName, + listOf( + RulesConfig("PACKAGE_NAME_INCORRECT", false, ""), + RulesConfig("PACKAGE_NAME_INCORRECT_PREFIX", false, "") ) - } + ) } diff --git a/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter1/PackageNamingFixTest.kt b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter1/PackageNamingFixTest.kt index 7bd28f6e0f..05e440277d 100644 --- a/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter1/PackageNamingFixTest.kt +++ b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter1/PackageNamingFixTest.kt @@ -1,15 +1,15 @@ package com.huawei.rri.fixbot.ruleset.huawei.chapter1 -import com.pinterest.ktlint.core.KtLint -import com.pinterest.ktlint.core.Rule -import com.pinterest.ktlint.core.RuleSet +import com.huawei.rri.fixbot.ruleset.huawei.rules.PackageNaming +import com.huawei.rri.fixbot.ruleset.huawei.utils.format import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import com.huawei.rri.fixbot.ruleset.huawei.rules.PackageNaming import test_framework.processing.TestComparatorUnit class PackageNamingFixTest { - val testComparatorUnit = TestComparatorUnit("test/paragraph1/naming/package", ::format) + private val testComparatorUnit = TestComparatorUnit("test/paragraph1/naming/package") { text, fileName -> + PackageNaming().format(text, fileName) + } @Test fun `incorrect case of package name (fix)`() { @@ -34,18 +34,4 @@ class PackageNamingFixTest { .compareFilesFromResources("FixUnderscoreExpected.kt", "FixUnderscoreTest.kt") ).isEqualTo(true) } - - - private fun format(text: String, fileName: String): String = PackageNaming().format(text, fileName) - - private fun Rule.format(text: String, fileName: String): String { - return KtLint.format( - KtLint.Params( - text = text, - ruleSets = listOf(RuleSet("huawei-codestyle", this@format)), - fileName = fileName, - cb = { _, _ -> } - ) - ) - } } diff --git a/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter1/PackagePathFixTest.kt b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter1/PackagePathFixTest.kt index e2fceb9815..9f11569f80 100644 --- a/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter1/PackagePathFixTest.kt +++ b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter1/PackagePathFixTest.kt @@ -1,16 +1,15 @@ package com.huawei.rri.fixbot.ruleset.huawei.chapter1 -import com.pinterest.ktlint.core.KtLint -import com.pinterest.ktlint.core.Rule -import com.pinterest.ktlint.core.RuleSet +import com.huawei.rri.fixbot.ruleset.huawei.rules.PackageNaming +import com.huawei.rri.fixbot.ruleset.huawei.utils.format import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import com.huawei.rri.fixbot.ruleset.huawei.rules.PackageNaming import test_framework.processing.TestComparatorUnit class PackagePathFixTest { private val testComparatorUnitWithDomain = TestComparatorUnit("test/paragraph1/naming/package/src/main/kotlin/com/huawei/some/name", ::format) private val testComparatorUnit = TestComparatorUnit("test/paragraph1/naming/package/src/main/kotlin/some", ::format) + private fun format(text: String, fileName: String): String = PackageNaming().format(text, fileName) @Test fun `fixing package name that differs from a path (fix)`() { @@ -43,18 +42,4 @@ class PackagePathFixTest { .compareFilesFromResources("FixMissingExpected.kt", "FixMissingTest.kt") ).isEqualTo(true) } - - - private fun format(text: String, fileName: String): String = PackageNaming().format(text, fileName) - - private fun Rule.format(text: String, fileName: String): String { - return KtLint.format( - KtLint.Params( - text = text, - ruleSets = listOf(RuleSet("huawei-codestyle", this@format)), - fileName = fileName, - cb = { _, _ -> } - ) - ) - } } diff --git a/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter2/KdocFormattingFixTest.kt b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter2/KdocFormattingFixTest.kt index beb1eafe32..ca39bdac03 100644 --- a/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter2/KdocFormattingFixTest.kt +++ b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter2/KdocFormattingFixTest.kt @@ -1,22 +1,17 @@ package com.huawei.rri.fixbot.ruleset.huawei.chapter2 -import com.huawei.rri.fixbot.ruleset.huawei.constants.Warnings -import com.huawei.rri.fixbot.ruleset.huawei.rules.KdocComments import com.huawei.rri.fixbot.ruleset.huawei.rules.KdocFormatting -import com.huawei.rri.fixbot.ruleset.huawei.rules.PackageNaming -import com.pinterest.ktlint.core.KtLint -import com.pinterest.ktlint.core.LintError -import com.pinterest.ktlint.core.Rule -import com.pinterest.ktlint.core.RuleSet -import com.pinterest.ktlint.test.lint +import com.huawei.rri.fixbot.ruleset.huawei.utils.format import org.assertj.core.api.Assertions +import org.junit.Assert import org.junit.Test import test_framework.processing.TestComparatorUnit class KdocFormattingFixTest { - val testComparatorUnit = TestComparatorUnit("test/paragraph2/kdoc/", ::format) - + private val testComparatorUnit = TestComparatorUnit("test/paragraph2/kdoc/") { text, fileName -> + KdocFormatting().format(text, fileName) + } @Test fun `there should be no blank line between kdoc and it's declaration code`() { @@ -26,17 +21,27 @@ class KdocFormattingFixTest { ).isEqualTo(true) } - private fun format(text: String, fileName: String): String = KdocFormatting().format(text, fileName) + @Test + fun `there should be exactly one white space after tag name`() { + Assert.assertTrue( + testComparatorUnit + .compareFilesFromResources("SpacesAfterTagExpected.kt", "SpacesAfterTagTest.kt") + ) + } - private fun Rule.format(text: String, fileName: String): String { - return KtLint.format( - KtLint.Params( - text = text, - ruleSets = listOf(RuleSet("huawei-codestyle", this@format)), - fileName = fileName, - cb = { _, _ -> } - ) + @Test + fun `tags should be ordered in KDocs`() { + Assert.assertTrue( + testComparatorUnit + .compareFilesFromResources("OrderedTagsExpected.kt", "OrderedTagsTest.kt") ) } + @Test + fun `special tags should have newline after them`() { + Assert.assertTrue( + testComparatorUnit + .compareFilesFromResources("SpecialTagsInKdocExpected.kt", "SpecialTagsInKdocTest.kt") + ) + } } diff --git a/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter2/KdocFormattingTest.kt b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter2/KdocFormattingTest.kt index 91e890b434..1c58ac725c 100644 --- a/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter2/KdocFormattingTest.kt +++ b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter2/KdocFormattingTest.kt @@ -1,7 +1,9 @@ package com.huawei.rri.fixbot.ruleset.huawei.chapter2 import com.huawei.rri.fixbot.ruleset.huawei.constants.Warnings +import com.huawei.rri.fixbot.ruleset.huawei.constants.Warnings.* import com.huawei.rri.fixbot.ruleset.huawei.rules.KdocFormatting +import com.huawei.rri.fixbot.ruleset.huawei.utils.lintMethod import com.pinterest.ktlint.core.LintError import com.pinterest.ktlint.test.lint import org.assertj.core.api.Assertions @@ -56,4 +58,162 @@ class KdocFormattingTest { LintError(31, 4, "kdoc-formatting", "${Warnings.BLANK_LINE_AFTER_KDOC.warnText()} someFunction") ) } + + private val funCode = """ + fun foo(a: Int): Int { + if (false) throw IllegalStateException() + return 2 * a + } + """.trimIndent() + + @Test + fun `@deprecated tag is not allowed`() { + val invalidCode = """ + /** + * @deprecated use foo instead + */ + fun bar() = Unit + """.trimIndent() + + lintMethod(KdocFormatting(), invalidCode, LintError(2, 4, "kdoc-formatting", + "${KDOC_NO_DEPRECATED_TAG.warnText()} @deprecated use foo instead")) + } + + @Test + fun `no empty descriptions in tag blocks are allowed`() { + val invalidCode = """ + /** + * @param a + * @return + * @throws IllegalStateException + */ + $funCode + """.trimIndent() + + lintMethod(KdocFormatting(), invalidCode, + LintError(3, 16, "kdoc-formatting", + "${KDOC_NO_EMPTY_TAGS.warnText()} @return", false), + LintError(3, 16, "kdoc-formatting", + "${KDOC_WRONG_SPACES_AFTER_TAG.warnText()} @return", false)) + } + + @Test + fun `KDocs should contain only one white space between tag and its content (positive example)`() { + val validCode = """ + /** + * @param a dummy int + * @return doubled value + * @throws IllegalStateException + */ + $funCode + """.trimIndent() + + lintMethod(KdocFormatting(), validCode) + } + + @Test + fun `KDocs should contain only one white space between tag and its content`() { + val invalidCode = """ + /** + * @param a dummy int + * @return doubled value + * @throws${'\t'}IllegalStateException + */ + $funCode + """.trimIndent() + + lintMethod(KdocFormatting(), invalidCode, + LintError(2, 16, "kdoc-formatting", + "${KDOC_WRONG_SPACES_AFTER_TAG.warnText()} @param", true), + LintError(3, 16, "kdoc-formatting", + "${KDOC_WRONG_SPACES_AFTER_TAG.warnText()} @return", true), + LintError(4, 16, "kdoc-formatting", + "${KDOC_WRONG_SPACES_AFTER_TAG.warnText()} @throws", true)) + } + + @Test + fun `tags should be ordered in KDocs (positive example)`() { + val validCode = """ + /** + * @param a dummy int + * @return doubled value + * @throws IllegalStateException + */ + $funCode + """.trimIndent() + + lintMethod(KdocFormatting(), validCode) + } + + @Test + fun `tags should be ordered in KDocs`() { + val invalidCode = """ + /** + * @return doubled value + * @throws IllegalStateException + * @param a dummy int + */ + $funCode + """.trimIndent() + + lintMethod(KdocFormatting(), invalidCode, + LintError(2, 16, "kdoc-formatting", + "${Warnings.KDOC_WRONG_TAGS_ORDER.warnText()} @return, @throws, @param", true)) + } + + @Test + fun `special tags should have exactly one newline after them (positive example)`() { + val validCode = """ + /** + * @implSpec stuff + * implementation details + * + * @apiNote foo + * + * @implNote bar + * + */ + $funCode + """.trimIndent() + + lintMethod(KdocFormatting(), validCode) + } + + @Test + fun `special tags should have exactly one newline after them (no newline)`() { + val invalidCode = """ + /** + * @implSpec stuff + * @apiNote foo + * @implNote bar + */ + $funCode + """.trimIndent() + + lintMethod(KdocFormatting(), invalidCode, + LintError(2, 16, "kdoc-formatting", + "${Warnings.KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS.warnText()} @implSpec, @apiNote, @implNote", true)) + } + + @Test + fun `special tags should have exactly one newline after them (many lines)`() { + val invalidCode = """ + /** + * @implSpec stuff + * + + * @apiNote foo + * + * + * + * @implNote bar + + */ + $funCode + """.trimIndent() + + lintMethod(KdocFormatting(), invalidCode, + LintError(2, 16, "kdoc-formatting", + "${Warnings.KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS.warnText()} @implSpec, @apiNote, @implNote", true)) + } } diff --git a/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter2/KdocSignatureTest.kt b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter2/KdocSignatureTest.kt index d84f9bfbc7..6333bdc270 100644 --- a/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter2/KdocSignatureTest.kt +++ b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/chapter2/KdocSignatureTest.kt @@ -4,6 +4,7 @@ import com.huawei.rri.fixbot.ruleset.huawei.constants.Warnings import com.huawei.rri.fixbot.ruleset.huawei.constants.Warnings.KDOC_WITHOUT_RETURN_TAG import com.huawei.rri.fixbot.ruleset.huawei.constants.Warnings.KDOC_WITHOUT_THROWS_TAG import com.huawei.rri.fixbot.ruleset.huawei.rules.KdocMethods +import com.huawei.rri.fixbot.ruleset.huawei.utils.lintMethod import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.LintError import com.pinterest.ktlint.core.RuleSet @@ -33,14 +34,14 @@ class KdocSignatureTest { $funCode """.trimIndent() - lintMethod(validCode) + lintMethod(KdocMethods(), validCode) } @Test fun `Warning should not be triggered for private functions`() { val validCode = "private $funCode" - lintMethod(validCode) + lintMethod(KdocMethods(), validCode) } @Test @@ -56,7 +57,7 @@ class KdocSignatureTest { } """.trimIndent() - lintMethod(validCode) + lintMethod(KdocMethods(), validCode) } @Test @@ -73,7 +74,7 @@ class KdocSignatureTest { $funCode """.trimIndent() - lintMethod(invalidCode, LintError(1, 13, "kdoc-methods", + lintMethod(KdocMethods(), invalidCode, LintError(1, 13, "kdoc-methods", "${Warnings.KDOC_WITHOUT_PARAM_TAG.warnText()} a") ) } @@ -93,7 +94,7 @@ class KdocSignatureTest { fun addInts(a: Int, b: Int): Int = a + b """.trimIndent() - lintMethod(invalidCode, LintError(1, 13, "kdoc-methods", + lintMethod(KdocMethods(), invalidCode, LintError(1, 13, "kdoc-methods", "${Warnings.KDOC_WITHOUT_PARAM_TAG.warnText()} b") ) } @@ -112,7 +113,7 @@ class KdocSignatureTest { $funCode """.trimIndent() - lintMethod(invalidCode, LintError(1, 13, "kdoc-methods", + lintMethod(KdocMethods(), invalidCode, LintError(1, 13, "kdoc-methods", "${KDOC_WITHOUT_RETURN_TAG.warnText()} ")) } @@ -130,7 +131,7 @@ class KdocSignatureTest { $funCode """.trimIndent() - lintMethod(invalidCode, LintError(1, 13, "kdoc-methods", + lintMethod(KdocMethods(), invalidCode, LintError(1, 13, "kdoc-methods", "${KDOC_WITHOUT_THROWS_TAG.warnText()} ")) } @@ -151,7 +152,7 @@ class KdocSignatureTest { } """.trimIndent() - lintMethod(invalidCode) + lintMethod(KdocMethods(), invalidCode) } @Test @@ -172,12 +173,4 @@ class KdocSignatureTest { .compareFilesFromResources("EmptyKdocFixed.kt", "EmptyKdocTest.kt") ) } - - private fun lintMethod(code: String, vararg lintErrors: LintError) { - assertThat( - KdocMethods().lint(code) - ).containsExactly( - *lintErrors - ) - } } diff --git a/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/utils/TestUtils.kt b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/utils/TestUtils.kt new file mode 100644 index 0000000000..78b8e8c51b --- /dev/null +++ b/diktat-huawei-rules/src/test/kotlin/com/huawei/rri/fixbot/ruleset/huawei/utils/TestUtils.kt @@ -0,0 +1,30 @@ +package com.huawei.rri.fixbot.ruleset.huawei.utils + +import com.pinterest.ktlint.core.KtLint +import com.pinterest.ktlint.core.LintError +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.RuleSet +import com.pinterest.ktlint.test.lint +import config.rules.RulesConfig +import org.assertj.core.api.Assertions + +fun lintMethod(rule: Rule, code: String, vararg lintErrors: LintError) { + Assertions.assertThat( + rule.lint(code) + ).containsExactly( + *lintErrors + ) +} + +internal fun Rule.format(text: String, fileName: String, + rulesConfigList: List? = emptyList()): String { + return KtLint.format( + KtLint.Params( + text = text, + ruleSets = listOf(RuleSet("huawei-codestyle", this@format)), + fileName = fileName, + rulesConfigList = rulesConfigList, + cb = { _, _ -> } + ) + ) +} diff --git a/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/OrderedTagsExpected.kt b/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/OrderedTagsExpected.kt new file mode 100644 index 0000000000..22731dafd4 --- /dev/null +++ b/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/OrderedTagsExpected.kt @@ -0,0 +1,12 @@ +package test.paragraph2.kdoc + +class OrderedTags { + + /** + * Empty function to test KDocs + * @param a useless integer + * @return Unit + * @throws RuntimeException never + */ + fun test(a: Int) = Unit +} diff --git a/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/OrderedTagsTest.kt b/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/OrderedTagsTest.kt new file mode 100644 index 0000000000..fbd0f1cf7a --- /dev/null +++ b/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/OrderedTagsTest.kt @@ -0,0 +1,12 @@ +package test.paragraph2.kdoc + +class OrderedTags { + + /** + * Empty function to test KDocs + * @return Unit + * @throws RuntimeException never + * @param a useless integer + */ + fun test(a: Int) = Unit +} diff --git a/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpacesAfterTagExpected.kt b/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpacesAfterTagExpected.kt new file mode 100644 index 0000000000..b57f2e5336 --- /dev/null +++ b/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpacesAfterTagExpected.kt @@ -0,0 +1,12 @@ +package test.paragraph2.kdoc + +class SpacesAfterTag { + + /** + * Empty function to test KDocs + * @param a useless integer + * @return + * @throws RuntimeException never + */ + fun test(a: Int) = Unit +} diff --git a/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpacesAfterTagTest.kt b/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpacesAfterTagTest.kt new file mode 100644 index 0000000000..86b757971c --- /dev/null +++ b/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpacesAfterTagTest.kt @@ -0,0 +1,12 @@ +package test.paragraph2.kdoc + +class SpacesAfterTag { + + /** + * Empty function to test KDocs + * @param a useless integer + * @return + * @throws RuntimeException never + */ + fun test(a: Int) = Unit +} diff --git a/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpecialTagsInKdocExpected.kt b/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpecialTagsInKdocExpected.kt new file mode 100644 index 0000000000..8410afedcd --- /dev/null +++ b/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpecialTagsInKdocExpected.kt @@ -0,0 +1,15 @@ +package test.paragraph2.kdoc + +class SpecialTagsInKdoc { + + /** + * Empty function to test KDocs + * @apiNote foo + * + * @implSpec bar + * + * @implNote baz + * + */ + fun test() = Unit +} diff --git a/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpecialTagsInKdocTest.kt b/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpecialTagsInKdocTest.kt new file mode 100644 index 0000000000..eb7d79deb4 --- /dev/null +++ b/diktat-huawei-rules/src/test/resources/test/paragraph2/kdoc/SpecialTagsInKdocTest.kt @@ -0,0 +1,14 @@ +package test.paragraph2.kdoc + +class SpecialTagsInKdoc { + + /** + * Empty function to test KDocs + * @apiNote foo + * @implSpec bar + * + * + * @implNote baz + */ + fun test() = Unit +} diff --git a/diktat-test-framework/src/main/kotlin/test_framework/processing/TestComparatorUnit.kt b/diktat-test-framework/src/main/kotlin/test_framework/processing/TestComparatorUnit.kt index 898a7d4d9f..7b76425a08 100644 --- a/diktat-test-framework/src/main/kotlin/test_framework/processing/TestComparatorUnit.kt +++ b/diktat-test-framework/src/main/kotlin/test_framework/processing/TestComparatorUnit.kt @@ -10,7 +10,7 @@ import java.nio.file.Paths import java.util.ArrayList import java.util.stream.Collectors -class TestComparatorUnit(private val resourceFilePath: String, private val function: (path: String, testFile: String) -> String) { +class TestComparatorUnit(private val resourceFilePath: String, private val function: (expectedText: String, testFilePath: String) -> String) { companion object { val log: Logger = LoggerFactory.getLogger(TestComparatorUnit::class.java) } diff --git a/rules-config.json b/rules-config.json index 95c708e1dc..4cec88c435 100644 --- a/rules-config.json +++ b/rules-config.json @@ -120,12 +120,32 @@ "configuration": "" }, { - "name": "KDOC_WITHOUT_THROWS_TAG", + "name": "INCORRECT_PACKAGE_SEPARATOR", "enabled": true, "configuration": "" }, { - "name": "INCORRECT_PACKAGE_SEPARATOR", + "name": "KDOC_NO_DEPRECATED_TAG", + "enabled": true, + "configuration": "" + }, + { + "name": "KDOC_NO_EMPTY_TAGS", + "enabled": true, + "configuration": "" + }, + { + "name": "KDOC_WRONG_SPACES_AFTER_TAG", + "enabled": true, + "configuration": "" + }, + { + "name": "KDOC_WRONG_TAGS_ORDER", + "enabled": true, + "configuration": "" + }, + { + "name": "KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS", "enabled": true, "configuration": "" }