diff --git a/diktat-rules/src/main/kotlin/generated/WarningNames.kt b/diktat-rules/src/main/kotlin/generated/WarningNames.kt index 77791e1ea6..6f2a0b2a83 100644 --- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt +++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt @@ -85,6 +85,9 @@ public object WarningNames { public const val KDOC_NO_CONSTRUCTOR_PROPERTY: String = "KDOC_NO_CONSTRUCTOR_PROPERTY" + public const val KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER: String = + "KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER" + public const val KDOC_EXTRA_PROPERTY: String = "KDOC_EXTRA_PROPERTY" public const val KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT: String = 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 8a63333015..baf74c0fe3 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 @@ -79,6 +79,7 @@ enum class Warnings( KDOC_NO_EMPTY_TAGS(false, "2.2.1", "no empty descriptions in tag blocks are allowed"), KDOC_NO_DEPRECATED_TAG(true, "2.1.3", "KDoc doesn't support @deprecated tag, use @Deprecated annotation instead"), KDOC_NO_CONSTRUCTOR_PROPERTY(true, "2.1.1", "all properties from the primary constructor should be documented in a @property tag in KDoc"), + KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER(true, "2.1.1", "only properties from the primary constructor should be documented in a @property tag in class KDoc"), KDOC_EXTRA_PROPERTY(false, "2.1.1", "There is property in KDoc which is not present in the class"), KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT(true, "2.1.1", "replace comment before property with @property tag in class KDoc"), KDOC_CONTAINS_DATE_OR_AUTHOR(false, "2.1.3", "KDoc should not contain creation date and author name"), diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocComments.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocComments.kt index e3da872ddb..ffbab8aad4 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocComments.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocComments.kt @@ -4,6 +4,7 @@ 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_EXTRA_PROPERTY +import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_CONSTRUCTOR_PROPERTY import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_CLASS_ELEMENTS @@ -106,18 +107,12 @@ class KdocComments(configRules: List) : DiktatRule( kdocBeforeClass?.let { checkKdocBeforeClass(node, kdocBeforeClass, prevComment) } - ?: run { - createKdocWithProperty(node, prevComment) - } + ?: createKdocWithProperty(node, prevComment) } - ?: run { - kdocBeforeClass?.let { - checkBasicKdocBeforeClass(node, kdocBeforeClass) - } - ?: run { - createKdocBasicKdoc(node) - } + ?: kdocBeforeClass?.let { + checkBasicKdocBeforeClass(node, kdocBeforeClass) } + ?: createKdocBasicKdoc(node) } @Suppress("UnsafeCallOnNullableType") @@ -151,6 +146,7 @@ class KdocComments(configRules: List) : DiktatRule( null } if (prevComment.elementType == KDOC || prevComment.elementType == BLOCK_COMMENT) { + // there is a documentation before property that we can extract, and there is class KDoc, where we can move it to handleKdocAndBlock(node, prevComment, kdocBeforeClass, propertyInClassKdoc, propertyInLocalKdoc) } else { KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT.warnAndFix(configRules, emitWarn, isFixMode, node.findChildByType(IDENTIFIER)!!.text, prevComment.startOffset, node) { @@ -205,21 +201,19 @@ class KdocComments(configRules: List) : DiktatRule( propertyInClassKdoc: ASTNode?, propertyInLocalKdoc: ASTNode?) { val kdocText = if (prevComment.elementType == KDOC) { - prevComment.text.removePrefix("/**").removeSuffix("*/") + prevComment.text.removePrefix("/**").removeSuffix("*/").trim('\n', ' ') } else { - prevComment.text.removePrefix("/*").removeSuffix("*/") + prevComment.text.removePrefix("/*").removeSuffix("*/").trim('\n', ' ') } - 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 property is documented with KDoc, which has a property tag inside, then it can contain some additional more complicated + // structure, that will be hard to move automatically + val isFixable = propertyInLocalKdoc == null + KDOC_NO_CONSTRUCTOR_PROPERTY.warnAndFix(configRules, emitWarn, isFixable, prevComment.text, prevComment.startOffset, node, isFixable) { + propertyInClassKdoc?.let { + // local docs should be appended to docs in class + appendKdocTagContent(propertyInClassKdoc, "\n$kdocText") } + ?: insertTextInKdoc(kdocBeforeClass, " * @property ${node.findChildByType(IDENTIFIER)!!.text} ${kdocText.removePrefix("*")}\n") if (prevComment.treeNext.elementType == WHITE_SPACE) { node.removeChild(prevComment.treeNext) @@ -256,6 +250,25 @@ class KdocComments(configRules: List) : DiktatRule( kdocBeforeClass.treeParent.replaceChild(kdocBeforeClass, KotlinParser().createNode(newKdocText).findChildByType(KDOC)!!) } + /** + * Append [content] to [kdocTagNode], e.g. + * (`@property foo bar`, "baz") -> `@property foo bar baz` + */ + private fun appendKdocTagContent( + kdocTagNode: ASTNode, content: String + ) { + kdocTagNode.findChildByType(KDOC_TEXT)?.let { + kdocTagNode.replaceChild( + it, + LeafPsiElement(KDOC_TEXT, "${it.text}$content") + ) + } + ?: kdocTagNode.addChild( + LeafPsiElement(KDOC_TEXT, content), + null + ) + } + private fun checkClassElements(node: ASTNode) { val modifier = node.getFirstChildWithType(MODIFIER_LIST) val classBody = node.getFirstChildWithType(CLASS_BODY) @@ -267,7 +280,22 @@ class KdocComments(configRules: List) : DiktatRule( .filterNot { (it.elementType == FUN && it.isStandardMethod()) || (it.elementType == FUN && it.isOverridden()) || (it.elementType == PROPERTY && it.isOverridden()) } - .forEach { checkDoc(it, MISSING_KDOC_CLASS_ELEMENTS) } + .forEach { classElement -> + if (classElement.elementType == PROPERTY) { + // we check if property declared in class body is also documented in class header via `@property` tag + val classKdoc = node.getFirstChildWithType(KDOC) + val propertyInClassKdoc = classKdoc?.kDocTags()?.find { + it.knownTag == KDocKnownTag.PROPERTY && it.getSubjectName() == classElement.getIdentifierName()?.text + } + propertyInClassKdoc?.let { + // if property is documented as `@property`, then we suggest to move docs to the declaration inside the class body + KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER.warn(configRules, emitWarn, isFixMode, classElement.text, classElement.startOffset, classElement) + return + } + } + // for everything else, we raise a missing kdoc warning + checkDoc(classElement, MISSING_KDOC_CLASS_ELEMENTS) + } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParser.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParser.kt index abeb23661b..44a01a6e20 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParser.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParser.kt @@ -72,7 +72,7 @@ class KotlinParser { * @return [ASTNode] * @throws KotlinParseException if code is incorrect */ - fun createNode(text: String, isPackage: Boolean = false) = makeNode(text, isPackage) ?: throw KotlinParseException("Your text is not valid") + fun createNode(text: String, isPackage: Boolean = false) = makeNode(text, isPackage) ?: throw KotlinParseException("Text is not valid: [$text]") /** * @param text kotlin code diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocCommentsWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocCommentsWarnTest.kt index af0880fb88..71ea6cc015 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocCommentsWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocCommentsWarnTest.kt @@ -1,6 +1,7 @@ package org.cqfn.diktat.ruleset.chapter2 import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_EXTRA_PROPERTY +import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_CONSTRUCTOR_PROPERTY import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_CLASS_ELEMENTS @@ -474,4 +475,38 @@ class KdocCommentsWarnTest : LintTestBase(::KdocComments) { LintError(3, 4, ruleId, "${KDOC_EXTRA_PROPERTY.warnText()} @property kek", false) ) } + + @Test + @Tag(WarningNames.KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER) + fun `property described only in class KDoc`() { + lintMethod( + """ + |/** + | * @property foo lorem ipsum + | */ + |class Example { + | val foo: Any + |} + """.trimMargin(), + LintError(5, 5, ruleId, "${KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER.warnText()} val foo: Any") + ) + } + + @Test + fun `property described both in class KDoc and own KDoc`() { + lintMethod( + """ + |/** + | * @property foo lorem ipsum + | */ + |class Example { + | /** + | * dolor sit amet + | */ + | val foo: Any + |} + """.trimMargin(), + LintError(5, 5, ruleId, "${KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER.warnText()} /**...") + ) + } } diff --git a/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentExpected.kt b/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentExpected.kt index c528ce2eb3..2e19ea6258 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentExpected.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/kdoc/ConstructorCommentExpected.kt @@ -2,8 +2,8 @@ package test.paragraph2.kdoc /** * @property name another text - * some text -*/ +* some text + */ class A ( var name: String ){} @@ -20,8 +20,8 @@ class A ( /** * @property name another text - * text -*/ +* text + */ class A ( val name: String ){} diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/FileComparator.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/FileComparator.kt index a90304a4b2..f5dac27e24 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/FileComparator.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/FileComparator.kt @@ -20,7 +20,7 @@ class FileComparator { private val actualResultList: List private val diffGenerator = DiffRowGenerator.create() .showInlineDiffs(true) - .mergeOriginalRevised(true) + .mergeOriginalRevised(false) .inlineDiffByWord(false) .oldTag { start -> if (start) "[" else "]" } .newTag { start -> if (start) "<" else ">" } @@ -55,15 +55,19 @@ class FileComparator { when (delta) { is ChangeDelta -> diffGenerator .generateDiffRows(delta.source.lines, delta.target.lines) - .joinToString(System.lineSeparator()) { it.oldLine } - .let { "[ChangeDelta, position ${delta.source.position}, lines: [$it]]" } + .joinToString(prefix = "ChangeDelta, position ${delta.source.position}, lines:\n", separator = "\n\n") { + """-${it.oldLine} + |+${it.newLine} + |""".trimMargin() + } + .let { "ChangeDelta, position ${delta.source.position}, lines:\n$it" } else -> delta.toString() } } log.error(""" |Expected result from <${expectedResultFile.name}> and actual formatted are different. - |See difference below (for ChangeDelta [text] indicates removed text, - inserted): + |See difference below: |$joinedDeltas """.trimMargin() ) diff --git a/info/available-rules.md b/info/available-rules.md index 66a1411355..7f47f2d701 100644 --- a/info/available-rules.md +++ b/info/available-rules.md @@ -39,6 +39,7 @@ | 2 | 2.2.1 | KDOC_NO_EMPTY_TAGS | Check: warns if KDoc tags have empty content | no | no | | | 2 | 2.1.3 | KDOC_NO_DEPRECATED_TAG | Check: warns if `@deprecated` is used in KDoc
Fix: adds `@Deprecated` annotation with message, removes tag | yes | no | Annotation's `replaceWith` field can be filled too| | 2 | 2.1.1 | KDOC_NO_CONSTRUCTOR_PROPERTY | Check: warns if there is no property tag inside KDoc before constructor | yes | no | +| 2 | 2.1.1 | KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER | Check: warns if property is declared in class body but documented with property tag inside KDoc before constructor | yes | no | | 2 | 2.1.1 | KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT| Check: warns if there is comment before property in constructor | yes | no | | 2 | 2.2.1 | KDOC_CONTAINS_DATE_OR_AUTHOR | Check: warns if header KDoc contains `@author` tag.
Warns if `@since` tag contains version and not date. | no | no | Detect author by other patterns (e.g. 'created by' etc.)| | 2 | 2.3.1 | KDOC_TRIVIAL_KDOC_ON_FUNCTION | Check: warns if KDoc contains single line with words 'return', 'get' or 'set' | no | no | | diff --git a/info/guide/diktat-coding-convention.md b/info/guide/diktat-coding-convention.md index 4512e99ea1..a77648739c 100644 --- a/info/guide/diktat-coding-convention.md +++ b/info/guide/diktat-coding-convention.md @@ -1130,8 +1130,93 @@ try { ### 3.5 Line length -Line length should be less than 120 symbols. The international code style prohibits `non-Latin` (`non-ASCII`) symbols. -(See [Identifiers](#r1.1.1)) However, if you still intend on using them, follow the following convention: +Line length should be less than 120 symbols. Otherwise, it should be split. + +If `complex property` initializing is too long, it should be split: + +**Invalid example:** +```kotlin +val complexProperty = 1 + 2 + 3 + 4 +``` +**Valid example:** +```kotlin +val complexProperty = 1 + 2 ++ 3 + 4 +``` + +If `annotation` is too long, it also should be split: + +**Invalid example:** +```kotlin +@Query(value = "select * from table where age = 10", nativeQuery = true) +fun foo() {} +``` +**Valid example:** +```kotlin +@Query(value = "select * from table " + + "where age = 10", nativeQuery = true) +fun foo() {} +``` + +Long one line `function` should be split: + +**Invalid example:** +```kotlin +fun foo() = goo().write("TooLong") +``` +**Valid example:** +```kotlin +fun foo() = + goo().write("TooLong") +``` + +Long `binary expression` should be split: + +**Invalid example:** +```kotlin +if (( x > 100) || y < 100 && !isFoo()) {} +``` + +**Valid example:** +```kotlin +if (( x > 100) || + y < 100 && !isFoo()) {} +``` + +`Eol comment` also can be split, but it depends on comment location. +If this comment is on the same line with code it should be on line before: + +**Invalid example:** +```kotlin +fun foo() { + val name = "Nick" // this comment is too long +} +``` +**Valid example:** +```kotlin +fun foo() { + // this comment is too long + val name = "Nick" +} +``` + +But if this comment is on new line - it should be split to several lines: + +**Invalid example:** +```kotlin +// This comment is too long. It should be on two lines. +fun foo() {} +``` + +**Valid example:** +```kotlin +// This comment is too long. +// It should be on two lines. +fun foo() {} +``` + +The international code style prohibits `non-Latin` (`non-ASCII`) symbols. (See [Identifiers](#r1.1.1)) However, if you still intend on using them, follow +the following convention: - One wide character occupies the width of two narrow characters. The "wide" and "narrow" parts of a character are defined by its [east Asian width Unicode attribute](https://unicode.org/reports/tr11/). diff --git a/info/rules-mapping.md b/info/rules-mapping.md index c5a31dcf37..c06dd90529 100644 --- a/info/rules-mapping.md +++ b/info/rules-mapping.md @@ -26,6 +26,7 @@ | MISSING_KDOC_CLASS_ELEMENTS | [2.1.1](guide/diktat-coding-convention.md#r2.1.1) | no | Comments | | MISSING_KDOC_ON_FUNCTION | [2.1.1](guide/diktat-coding-convention.md#r2.1.1) | yes | Comments | | KDOC_NO_CONSTRUCTOR_PROPERTY | [2.1.1](guide/diktat-coding-convention.md#r2.1.1) | yes | Comments | +| KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER | [2.1.1](guide/diktat-coding-convention.md#r2.1.1) | yes | Comments | | KDOC_EXTRA_PROPERTY | [2.1.1](guide/diktat-coding-convention.md#r2.1.1) | no | Comments | | KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT | [2.1.1](guide/diktat-coding-convention.md#r2.1.1) | yes | Comments | | KDOC_WITHOUT_PARAM_TAG | [2.1.2](guide/diktat-coding-convention.md#r2.1.2) | yes | Comments |