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 b35f08954d..3d07080314 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 @@ -6,6 +6,8 @@ import org.cqfn.diktat.common.config.rules.isRuleEnabled import org.cqfn.diktat.ruleset.utils.hasSuppress import org.jetbrains.kotlin.com.intellij.lang.ASTNode +typealias EmitType = ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) + /** * This class represent individual inspections of diktat code style. * A [Warnings] entry contains rule name, warning message and is used in code check. @@ -135,7 +137,7 @@ enum class Warnings(private val canBeAutoCorrected: Boolean, private val warn: S @Suppress("LongParameterList") fun warnAndFix(configRules: List, - emit: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit), + emit: EmitType, isFixMode: Boolean, freeText: String, offset: Int, @@ -148,7 +150,7 @@ enum class Warnings(private val canBeAutoCorrected: Boolean, private val warn: S @Suppress("LongParameterList") fun warn(configs: List, - emit: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit), + emit: EmitType, autoCorrected: Boolean, freeText: String, offset: Int, diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/CommentsFormatting.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/CommentsFormatting.kt index 80fa96127f..f007a28682 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/CommentsFormatting.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/CommentsFormatting.kt @@ -1,5 +1,22 @@ package org.cqfn.diktat.ruleset.rules.kdoc +import org.cqfn.diktat.common.config.rules.RuleConfiguration +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.common.config.rules.getRuleConfig +import org.cqfn.diktat.ruleset.constants.EmitType +import org.cqfn.diktat.ruleset.constants.Warnings.COMMENT_WHITE_SPACE +import org.cqfn.diktat.ruleset.constants.Warnings.FIRST_COMMENT_NO_SPACES +import org.cqfn.diktat.ruleset.constants.Warnings.IF_ELSE_COMMENTS +import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_NEWLINES_AROUND_KDOC +import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findChildrenMatching +import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType +import org.cqfn.diktat.ruleset.utils.getFirstChildWithType +import org.cqfn.diktat.ruleset.utils.hasChildOfType +import org.cqfn.diktat.ruleset.utils.leaveOnlyOneNewLine +import org.cqfn.diktat.ruleset.utils.numNewLines +import org.cqfn.diktat.ruleset.utils.prevNodeUntilNode + import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.ast.ElementType.BLOCK import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT @@ -23,14 +40,6 @@ import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE import com.pinterest.ktlint.core.ast.isWhiteSpace import com.pinterest.ktlint.core.ast.prevSibling -import org.cqfn.diktat.common.config.rules.RuleConfiguration -import org.cqfn.diktat.common.config.rules.RulesConfig -import org.cqfn.diktat.common.config.rules.getRuleConfig -import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_NEWLINES_AROUND_KDOC -import org.cqfn.diktat.ruleset.constants.Warnings.COMMENT_WHITE_SPACE -import org.cqfn.diktat.ruleset.constants.Warnings.FIRST_COMMENT_NO_SPACES -import org.cqfn.diktat.ruleset.constants.Warnings.IF_ELSE_COMMENTS -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 @@ -49,17 +58,17 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType * * Comments in if else should be inside code blocks. Exception: General if comment */ class CommentsFormatting(private val configRules: List) : Rule("kdoc-comments-codeblocks-formatting") { - companion object { - private const val MAX_SPACES = 1 - private const val APPROPRIATE_COMMENT_SPACES = 1 - } - - private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) private var isFixMode: Boolean = false + private lateinit var emitWarn: EmitType + /** + * @param node + * @param autoCorrect + * @param emit + */ override fun visit(node: ASTNode, autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { + emit: EmitType) { isFixMode = autoCorrect emitWarn = emit @@ -75,6 +84,9 @@ class CommentsFormatting(private val configRules: List) : Rule("kdo IF -> handleIfElse(node) EOL_COMMENT, BLOCK_COMMENT -> handleEolAndBlockComments(node, configuration) KDOC -> handleKdocComments(node, configuration) + else -> { + // this is a generated else block + } } } @@ -90,7 +102,7 @@ class CommentsFormatting(private val configRules: List) : Rule("kdo private fun handleKdocComments(node: ASTNode, configuration: CommentsFormattingConfiguration) { if (node.treeParent.treeParent != null && node.treeParent.treeParent.elementType == BLOCK) { - checkCommentsInCodeBlocks(node.treeParent) // node.treeParent is a node that contains a comment. + checkCommentsInCodeBlocks(node.treeParent) // node.treeParent is a node that contains a comment. } else if (node.treeParent.elementType != IF) { checkClassComment(node) } @@ -109,9 +121,10 @@ class CommentsFormatting(private val configRules: List) : Rule("kdo // Checking if comment is inside a code block like fun{} // Not checking last comment as well if (isFirstComment(node)) { - if (node.isBlockOrClassBody()) - // Just check white spaces before comment + if (node.isBlockOrClassBody()) { + // Just check white spaces before comment checkFirstCommentSpaces(node) + } return } } else if (node.treeParent.lastChildNode != node && node.treeParent.elementType != IF && @@ -135,33 +148,44 @@ class CommentsFormatting(private val configRules: List) : Rule("kdo val copyComment = comment?.copyElement() if (comment != null) { IF_ELSE_COMMENTS.warnAndFix(configRules, emitWarn, isFixMode, comment.text, node.startOffset, node) { - if (elseBlock.hasChildOfType(BLOCK)) { - val elseCodeBlock = elseBlock.getFirstChildWithType(BLOCK)!! - elseCodeBlock.addChild(copyComment!!, - elseCodeBlock.firstChildNode.treeNext) - elseCodeBlock.addChild(PsiWhiteSpaceImpl("\n"), - elseCodeBlock.firstChildNode.treeNext) - node.removeChild(comment) - } else { - elseKeyWord.treeParent.addChild(copyComment!!, elseKeyWord.treeNext) - elseKeyWord.treeParent.addChild(PsiWhiteSpaceImpl("\n"), elseKeyWord.treeNext) - node.removeChild(comment) - } - - val whiteSpace = elseKeyWord.prevNodeUntilNode(THEN, WHITE_SPACE) - - if (whiteSpace != null) - node.removeChild(whiteSpace) + moveCommentToElse(node, elseBlock, elseKeyWord, comment, copyComment) } } } } + @Suppress("UnsafeCallOnNullableType") + private fun moveCommentToElse(node: ASTNode, + elseBlock: ASTNode, + elseKeyWord: ASTNode, + comment: ASTNode, + copyComment: ASTNode?) { + if (elseBlock.hasChildOfType(BLOCK)) { + val elseCodeBlock = elseBlock.getFirstChildWithType(BLOCK)!! + elseCodeBlock.addChild(copyComment!!, + elseCodeBlock.firstChildNode.treeNext) + elseCodeBlock.addChild(PsiWhiteSpaceImpl("\n"), + elseCodeBlock.firstChildNode.treeNext) + node.removeChild(comment) + } else { + elseKeyWord.treeParent.addChild(copyComment!!, elseKeyWord.treeNext) + elseKeyWord.treeParent.addChild(PsiWhiteSpaceImpl("\n"), elseKeyWord.treeNext) + node.removeChild(comment) + } + + val whiteSpace = elseKeyWord.prevNodeUntilNode(THEN, WHITE_SPACE) + + if (whiteSpace != null) { + node.removeChild(whiteSpace) + } + } + private fun checkCommentsInCodeBlocks(node: ASTNode) { if (isFirstComment(node)) { - if (node.isBlockOrClassBody()) + if (node.isBlockOrClassBody()) { // Just check white spaces before comment checkFirstCommentSpaces(node) + } return } @@ -172,7 +196,7 @@ class CommentsFormatting(private val configRules: List) : Rule("kdo } } else { if (node.treePrev.numNewLines() == 1 || node.treePrev.numNewLines() > 2) { - WRONG_NEWLINES_AROUND_KDOC.warnAndFix(configRules, emitWarn, isFixMode, node.text, node.startOffset,node) { + WRONG_NEWLINES_AROUND_KDOC.warnAndFix(configRules, emitWarn, isFixMode, node.text, node.startOffset, node) { (node.treePrev as LeafPsiElement).replaceWithText("\n\n") } } @@ -197,29 +221,45 @@ class CommentsFormatting(private val configRules: List) : Rule("kdo } } - @Suppress("ComplexMethod") + @Suppress("ComplexMethod", "TOO_LONG_FUNCTION") private fun checkWhiteSpaceBeforeComment(node: ASTNode, configuration: CommentsFormattingConfiguration) { if (node.elementType == EOL_COMMENT && - node.text.trimStart('/').takeWhile { it == ' ' }.length == configuration.maxSpacesInComment) + node + .text + .trimStart('/') + .takeWhile { it == ' ' } + .length == configuration.maxSpacesInComment) { return + } if (node.elementType == BLOCK_COMMENT && - (node.text.trim('/', '*').takeWhile { it == ' ' }.length == configuration.maxSpacesInComment || - node.text.trim('/', '*').takeWhile { it == '\n' }.isNotEmpty())) { + (node + .text + .trim('/', '*') + .takeWhile { it == ' ' } + .length == configuration.maxSpacesInComment || + node + .text + .trim('/', '*') + .takeWhile { it == '\n' } + .isNotEmpty())) { return } if (node.elementType == KDOC) { val section = node.getFirstChildWithType(KDOC_SECTION) if (section != null && - section.findChildrenMatching(KDOC_TEXT){ (it.treePrev != null && it.treePrev.elementType == KDOC_LEADING_ASTERISK) || it.treePrev == null } - .all { it.text.startsWith(" ".repeat(configuration.maxSpacesInComment)) }) // it.treePrev == null if there is no \n at the beginning of KDoc + section.findChildrenMatching(KDOC_TEXT) { (it.treePrev != null && it.treePrev.elementType == KDOC_LEADING_ASTERISK) || it.treePrev == null } + .all { it.text.startsWith(" ".repeat(configuration.maxSpacesInComment)) }) { + // it.treePrev == null if there is no \n at the beginning of KDoc return + } if (section != null && section.getAllChildrenWithType(KDOC_CODE_BLOCK_TEXT).isNotEmpty() && - section.getAllChildrenWithType(KDOC_CODE_BLOCK_TEXT).all { it.text.startsWith(" ".repeat(configuration.maxSpacesInComment)) }) + section.getAllChildrenWithType(KDOC_CODE_BLOCK_TEXT).all { it.text.startsWith(" ".repeat(configuration.maxSpacesInComment)) }) { return + } } COMMENT_WHITE_SPACE.warnAndFix(configRules, emitWarn, isFixMode, @@ -258,7 +298,7 @@ class CommentsFormatting(private val configRules: List) : Rule("kdo } } else if (node.treeParent.elementType != FILE && !node.treeParent.treePrev.isWhiteSpace()) { // fixme: we might face more issues because newline node is inserted in a wrong place which causes consecutive - // white spaces to be split among nodes on different levels. But this requires investigation. + // white spaces to be split among nodes on different levels. But this requires investigation. WRONG_NEWLINES_AROUND_KDOC.warnAndFix(configRules, emitWarn, isFixMode, node.text, node.startOffset, node) { node.treeParent.treeParent.addChild(PsiWhiteSpaceImpl("\n"), node.treeParent) } @@ -282,30 +322,42 @@ class CommentsFormatting(private val configRules: List) : Rule("kdo } } - private fun isFirstComment(node: ASTNode): Boolean { - return if (node.isBlockOrClassBody()) { - // In case when comment is inside of a function or class - if (node.treePrev.isWhiteSpace()) { - node.treePrev.treePrev.elementType == LBRACE - } else { - node.treePrev == null || node.treePrev.elementType == LBRACE // null is handled for functional literal - } - } else if (node.treeParent?.treeParent?.elementType == FILE && node.treeParent.prevSibling { it.text.isNotBlank() } == null) { - // `treeParent` is the first not-empty node in a file - true - } else if (node.treeParent.elementType != FILE && node.treeParent.treePrev != null && - node.treeParent.treePrev.treePrev != null) { - // When comment inside of a PROPERTY - node.treeParent.treePrev.treePrev.elementType == LBRACE + private fun isFirstComment(node: ASTNode) = if (node.isBlockOrClassBody()) { + // In case when comment is inside of a function or class + if (node.treePrev.isWhiteSpace()) { + node.treePrev.treePrev.elementType == LBRACE } else { - node.treeParent.getAllChildrenWithType(node.elementType).first() == node + node.treePrev == null || node.treePrev.elementType == LBRACE // null is handled for functional literal } + } else if (node.treeParent?.treeParent?.elementType == FILE && node.treeParent.prevSibling { it.text.isNotBlank() } == null) { + // `treeParent` is the first not-empty node in a file + true + } else if (node.treeParent.elementType != FILE && node.treeParent.treePrev != null && + node.treeParent.treePrev.treePrev != null) { + // When comment inside of a PROPERTY + node.treeParent.treePrev.treePrev.elementType == LBRACE + } else { + node.treeParent.getAllChildrenWithType(node.elementType).first() == node } - private fun ASTNode.isBlockOrClassBody() : Boolean = treeParent.elementType == BLOCK || treeParent.elementType == CLASS_BODY + private fun ASTNode.isBlockOrClassBody(): Boolean = treeParent.elementType == BLOCK || treeParent.elementType == CLASS_BODY + /** + * [RuleConfiguration] for [CommentsFormatting] rule + */ class CommentsFormattingConfiguration(config: Map) : RuleConfiguration(config) { + /** + * Number of spaces before comment start + */ val maxSpacesBeforeComment = config["maxSpacesBeforeComment"]?.toIntOrNull() ?: MAX_SPACES + + /** + * Number of spaces after comment sign (`//` or other) + */ val maxSpacesInComment = config["maxSpacesInComment"]?.toIntOrNull() ?: APPROPRIATE_COMMENT_SPACES } + companion object { + private const val APPROPRIATE_COMMENT_SPACES = 1 + private const val MAX_SPACES = 1 + } } 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 18e6d45d52..bf5689a83a 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,5 +1,26 @@ package org.cqfn.diktat.ruleset.rules.kdoc +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.common.config.rules.getCommonConfiguration +import org.cqfn.diktat.ruleset.constants.EmitType +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.KotlinParser +import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType +import org.cqfn.diktat.ruleset.utils.getFileName +import org.cqfn.diktat.ruleset.utils.getFirstChildWithType +import org.cqfn.diktat.ruleset.utils.getIdentifierName +import org.cqfn.diktat.ruleset.utils.getRootNode +import org.cqfn.diktat.ruleset.utils.hasChildOfType +import org.cqfn.diktat.ruleset.utils.hasTestAnnotation +import org.cqfn.diktat.ruleset.utils.isAccessibleOutside +import org.cqfn.diktat.ruleset.utils.isLocatedInTest +import org.cqfn.diktat.ruleset.utils.isStandardMethod +import org.cqfn.diktat.ruleset.utils.kDocTags +import org.cqfn.diktat.ruleset.utils.splitPathToDirs + import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT import com.pinterest.ktlint.core.ast.ElementType.CLASS @@ -19,13 +40,6 @@ 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 @@ -40,36 +54,42 @@ import org.jetbrains.kotlin.psi.psiUtil.parents * 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) - } - - private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) private var isFixMode: Boolean = false + private lateinit var emitWarn: EmitType + /** + * @param node + * @param autoCorrect + * @param emit + */ override fun visit( node: ASTNode, autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit + emit: EmitType ) { emitWarn = emit isFixMode = autoCorrect val config = configRules.getCommonConfiguration().value val fileName = node.getRootNode().getFileName() - if (!(node.hasTestAnnotation() || isLocatedInTest(fileName.splitPathToDirs(), config.testAnchors))) + if (!(node.hasTestAnnotation() || isLocatedInTest(fileName.splitPathToDirs(), config.testAnchors))) { when (node.elementType) { FILE -> checkTopLevelDoc(node) CLASS -> checkClassElements(node) VALUE_PARAMETER -> checkValueParameter(node) + else -> { + // this is a generated else block + } } + } } - @Suppress("UnsafeCallOnNullableType") + @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION") private fun checkValueParameter(node: ASTNode) { if (node.parents().none { it.elementType == PRIMARY_CONSTRUCTOR } || - !(node.hasChildOfType(VAL_KEYWORD) || node.hasChildOfType(VAR_KEYWORD))) return + !(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)) { @@ -81,129 +101,153 @@ class KdocComments(private val configRules: List) : Rule("kdoc-comm } else { null } - val kDocBeforeClass = node.parent({ it.elementType == CLASS })!!.findChildByType(KDOC) + val kdocBeforeClass = node.parent({ it.elementType == CLASS })!!.findChildByType(KDOC) if (prevComment != null) { - if (kDocBeforeClass != null) - checkKDocBeforeClass(node, kDocBeforeClass, prevComment) - else - createKDocWithProperty(node, prevComment) + if (kdocBeforeClass != null) { + checkKdocBeforeClass(node, kdocBeforeClass, prevComment) + } else { + createKdocWithProperty(node, prevComment) + } } else { - if (kDocBeforeClass != null) - checkBasicKDocBeforeClass(node, kDocBeforeClass) - else - createKDocBasicKDoc(node) + if (kdocBeforeClass != null) { + checkBasicKdocBeforeClass(node, kdocBeforeClass) + } else { + createKdocBasicKdoc(node) + } } } @Suppress("UnsafeCallOnNullableType") - private fun checkBasicKDocBeforeClass(node: ASTNode, kDocBeforeClass: ASTNode) { - val propertyInClassKDoc = kDocBeforeClass + 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()) { + 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") + insertTextInKdoc(kdocBeforeClass, " * @property ${node.findChildByType(IDENTIFIER)!!.text}\n") } } } @Suppress("UnsafeCallOnNullableType") - private fun checkKDocBeforeClass(node: ASTNode, kDocBeforeClass: ASTNode, prevComment: ASTNode) { - val propertyInClassKDoc = kDocBeforeClass + 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) + val propertyInLocalKdoc = if (prevComment.elementType == KDOC) { prevComment .kDocTags() ?.firstOrNull { it.knownTag == KDocKnownTag.PROPERTY && it.getSubjectName() == node.findChildByType(IDENTIFIER)!!.text } ?.node - else + } else { null + } if (prevComment.elementType == KDOC || prevComment.elementType == BLOCK_COMMENT) { - handleKDcoAndBlock(node, prevComment, kDocBeforeClass, propertyInClassKDoc, propertyInLocalKDoc) + handleKdocAndBlock(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) + handleCommentBefore(node, kdocBeforeClass, prevComment, propertyInClassKdoc) } } } @Suppress("UnsafeCallOnNullableType") - private fun createKDocBasicKDoc(node: ASTNode) { + 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 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) + classNode.addChild(newKdoc.findChildByType(KDOC)!!, classNode.firstChildNode) } } @Suppress("UnsafeCallOnNullableType") - private fun createKDocWithProperty(node: ASTNode, prevComment: ASTNode) { + 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) { + 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)!! + val newKdoc = KotlinParser().createNode(newKdocText).findChildByType(KDOC)!! classNode.addChild(PsiWhiteSpaceImpl("\n"), classNode.firstChildNode) - classNode.addChild(newKDoc, classNode.firstChildNode) + classNode.addChild(newKdoc, classNode.firstChildNode) if (prevComment.elementType == EOL_COMMENT) { node.treeParent.removeRange(prevComment, node) } else { - if (prevComment.treeNext.elementType == WHITE_SPACE) + 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) + private fun handleKdocAndBlock( + node: ASTNode, + prevComment: ASTNode, + kdocBeforeClass: ASTNode, + propertyInClassKdoc: ASTNode?, + propertyInLocalKdoc: ASTNode?) { + val kdocText = if (prevComment.elementType == KDOC) { prevComment.text.removePrefix("/**").removeSuffix("*/") - else + } else { prevComment.text.removePrefix("/*").removeSuffix("*/") - val isFixable = (propertyInClassKDoc != null && propertyInLocalKDoc != null) || - (propertyInClassKDoc == null && propertyInLocalKDoc == null && kDocText.replace("\n+".toRegex(), "").lines().size != 1) + } + 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 (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) + 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)!! + 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) + propertyInClassKdoc.addChild(LeafPsiElement(KDOC_TEXT, prevComment.text), null) } } else { - insertTextInKDoc(kDocBeforeClass, "* @property ${node.findChildByType(IDENTIFIER)!!.text} ${prevComment.text.removeRange(0, 2)}\n") + 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 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) { @@ -224,7 +268,6 @@ class KdocComments(private val configRules: List) : Rule("kdoc-comm (node.getAllChildrenWithType(CLASS) + node.getAllChildrenWithType(FUN)) .forEach { checkDoc(it, MISSING_KDOC_TOP_LEVEL) } - /** * raises warning if protected, public or internal code element does not have a Kdoc */ @@ -238,4 +281,7 @@ class KdocComments(private val configRules: List) : Rule("kdoc-comm warning.warn(configRules, emitWarn, isFixMode, name!!.text, node.startOffset, node) } } + companion object { + private val statementsToDocument = TokenSet.create(CLASS, FUN, PROPERTY) + } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocFormatting.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocFormatting.kt index 132552692d..1cbf3bd793 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocFormatting.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocFormatting.kt @@ -1,21 +1,7 @@ package org.cqfn.diktat.ruleset.rules.kdoc -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_SECTION -import com.pinterest.ktlint.core.ast.ElementType.KDOC_TAG -import com.pinterest.ktlint.core.ast.ElementType.KDOC_TAG_NAME -import com.pinterest.ktlint.core.ast.ElementType.KDOC_TEXT -import com.pinterest.ktlint.core.ast.ElementType.PROPERTY -import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE -import com.pinterest.ktlint.core.ast.isWhiteSpaceWithNewline -import com.pinterest.ktlint.core.ast.nextSibling -import com.pinterest.ktlint.core.ast.prevSibling import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.EmitType import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_EMPTY_KDOC import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NEWLINES_BEFORE_BASIC_TAGS import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_DEPRECATED_TAG @@ -35,6 +21,22 @@ import org.cqfn.diktat.ruleset.utils.getRootNode import org.cqfn.diktat.ruleset.utils.hasChildMatching import org.cqfn.diktat.ruleset.utils.kDocTags import org.cqfn.diktat.ruleset.utils.leaveOnlyOneNewLine + +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_SECTION +import com.pinterest.ktlint.core.ast.ElementType.KDOC_TAG +import com.pinterest.ktlint.core.ast.ElementType.KDOC_TAG_NAME +import com.pinterest.ktlint.core.ast.ElementType.KDOC_TEXT +import com.pinterest.ktlint.core.ast.ElementType.PROPERTY +import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE +import com.pinterest.ktlint.core.ast.isWhiteSpaceWithNewline +import com.pinterest.ktlint.core.ast.nextSibling +import com.pinterest.ktlint.core.ast.prevSibling import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement @@ -58,21 +60,25 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset class KdocFormatting(private val configRules: List) : Rule("kdoc-formatting") { private val basicTagsList = listOf(KDocKnownTag.PARAM, KDocKnownTag.RETURN, KDocKnownTag.THROWS) private val specialTagNames = setOf("implSpec", "implNote", "apiNote") - - private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) - private var isFixMode: Boolean = false private var fileName: String = "" + private var isFixMode: Boolean = false + private lateinit var emitWarn: EmitType + /** + * @param node + * @param autoCorrect + * @param emit + */ override fun visit(node: ASTNode, autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { + emit: EmitType) { isFixMode = autoCorrect emitWarn = emit fileName = node.getRootNode().getFileName() val declarationTypes = setOf(CLASS, FUN, PROPERTY) - if (node.elementType == KDOC && checkKdocNotEmpty(node)) { + if (node.elementType == KDOC && isKdocNotEmpty(node)) { checkNoDeprecatedTag(node) checkEmptyTags(node.kDocTags()) checkSpaceAfterTag(node.kDocTags()) @@ -83,41 +89,42 @@ class KdocFormatting(private val configRules: List) : Rule("kdoc-fo } } - private fun checkKdocNotEmpty(node: ASTNode): Boolean { + private fun isKdocNotEmpty(node: ASTNode): Boolean { val isKdocNotEmpty = node.getFirstChildWithType(KDOC_SECTION) ?.hasChildMatching { it.elementType != KDOC_LEADING_ASTERISK && it.elementType != WHITE_SPACE } ?: false if (!isKdocNotEmpty) { KDOC_EMPTY_KDOC.warn(configRules, emitWarn, isFixMode, - node.treeParent.getIdentifierName()?.text - ?: node.nextSibling { it.elementType in KtTokens.KEYWORDS }?.text - ?: fileName, node.startOffset, node) + node.treeParent.getIdentifierName()?.text + ?: node.nextSibling { it.elementType in KtTokens.KEYWORDS }?.text + ?: fileName, node.startOffset, node) } return isKdocNotEmpty } @Suppress("UnsafeCallOnNullableType") private fun checkNoDeprecatedTag(node: ASTNode) { - val kDocTags = node.kDocTags() - kDocTags?.find { it.name == "deprecated" } - ?.let { kDocTag -> - KDOC_NO_DEPRECATED_TAG.warnAndFix(configRules, emitWarn, isFixMode, kDocTag.text, kDocTag.node.startOffset, kDocTag.node) { - val kDocSection = kDocTag.node.treeParent - val deprecatedTagNode = kDocTag.node - kDocSection.removeRange(deprecatedTagNode.prevSibling { it.elementType == WHITE_SPACE }!!, - deprecatedTagNode.nextSibling { it.elementType == WHITE_SPACE } - ) - node.treeParent.addChild(LeafPsiElement(ElementType.ANNOTATION, - "@Deprecated(message = \"${kDocTag.getContent()}\")"), node.treeNext) - // copy to get all necessary indents - node.treeParent.addChild(node.nextSibling { it.elementType == WHITE_SPACE }!!.clone() as PsiWhiteSpaceImpl, node.treeNext) } + val kdocTags = node.kDocTags() + kdocTags?.find { it.name == "deprecated" } + ?.let { kdocTag -> + KDOC_NO_DEPRECATED_TAG.warnAndFix(configRules, emitWarn, isFixMode, kdocTag.text, kdocTag.node.startOffset, kdocTag.node) { + val kdocSection = kdocTag.node.treeParent + val deprecatedTagNode = kdocTag.node + kdocSection.removeRange(deprecatedTagNode.prevSibling { it.elementType == WHITE_SPACE }!!, + deprecatedTagNode.nextSibling { it.elementType == WHITE_SPACE } + ) + node.treeParent.addChild(LeafPsiElement(ElementType.ANNOTATION, + "@Deprecated(message = \"${kdocTag.getContent()}\")"), node.treeNext) + // copy to get all necessary indents + node.treeParent.addChild(node.nextSibling { it.elementType == WHITE_SPACE }!!.clone() as PsiWhiteSpaceImpl, node.treeNext) + } } } @Suppress("UnsafeCallOnNullableType") - private fun checkEmptyTags(kDocTags: Collection?) { - kDocTags?.filter { + private fun checkEmptyTags(kdocTags: Collection?) { + kdocTags?.filter { it.getSubjectName() == null && it.getContent().isEmpty() }?.forEach { KDOC_NO_EMPTY_TAGS.warn(configRules, emitWarn, isFixMode, "@${it.name!!}", it.node.startOffset, it.node) @@ -125,19 +132,21 @@ class KdocFormatting(private val configRules: List) : Rule("kdoc-fo } @Suppress("UnsafeCallOnNullableType") - private fun checkSpaceAfterTag(kDocTags: Collection?) { + private fun checkSpaceAfterTag(kdocTags: Collection?) { // tags can have 'parameters' and content, either can be missing // we always can find white space after tag name, but after tag parameters only if content is present - kDocTags?.filter { tag -> + kdocTags?.filter { tag -> val hasSubject = tag.getSubjectName()?.isNotBlank() ?: false - if (!hasSubject && tag.getContent().isBlank()) return@filter false + if (!hasSubject && tag.getContent().isBlank()) { + return@filter false + } val (isSpaceBeforeContentError, isSpaceAfterTagError) = findBeforeAndAfterSpaces(tag) hasSubject && isSpaceBeforeContentError || isSpaceAfterTagError }?.forEach { tag -> KDOC_WRONG_SPACES_AFTER_TAG.warnAndFix(configRules, emitWarn, isFixMode, - "@${tag.name!!}", tag.node.startOffset, tag.node) { + "@${tag.name!!}", tag.node.startOffset, tag.node) { tag.node.findChildBefore(KDOC_TEXT, WHITE_SPACE) ?.let { tag.node.replaceChild(it, LeafPsiElement(WHITE_SPACE, " ")) } tag.node.findChildAfter(KDOC_TAG_NAME, WHITE_SPACE) @@ -146,69 +155,79 @@ class KdocFormatting(private val configRules: List) : Rule("kdoc-fo } } - private fun findBeforeAndAfterSpaces(tag: KDocTag): Pair { - return Pair(tag.node.findChildBefore(KDOC_TEXT, WHITE_SPACE).let { - it?.text != " " - && !(it?.isWhiteSpaceWithNewline() ?: false) - }, - tag.node.findChildAfter(KDOC_TAG_NAME, WHITE_SPACE).let { - it?.text != " " - && !(it?.isWhiteSpaceWithNewline() ?: false) - } - ) - } + private fun findBeforeAndAfterSpaces(tag: KDocTag) = Pair(tag.node.findChildBefore(KDOC_TEXT, WHITE_SPACE).let { + it?.text != " " && + !(it?.isWhiteSpaceWithNewline() ?: false) + }, + tag.node.findChildAfter(KDOC_TAG_NAME, WHITE_SPACE).let { + it?.text != " " && + !(it?.isWhiteSpaceWithNewline() ?: false) + } + ) - @Suppress("UnsafeCallOnNullableType") + @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION") private fun checkBasicTagsOrder(node: ASTNode) { - val kDocTags = node.kDocTags() + val kdocTags = node.kDocTags() // distinct basic tags which are present in current KDoc, in proper order val basicTagsOrdered = basicTagsList.filter { basicTag -> - kDocTags?.find { it.knownTag == basicTag } != null + kdocTags?.find { it.knownTag == basicTag } != null } // all basic tags from current KDoc - val basicTags = kDocTags?.filter { basicTagsOrdered.contains(it.knownTag) } + val basicTags = kdocTags?.filter { basicTagsOrdered.contains(it.knownTag) } val isTagsInCorrectOrder = basicTags - ?.fold(mutableListOf()) { acc, kDocTag -> - if (acc.size > 0 && acc.last().knownTag != kDocTag.knownTag) acc.add(kDocTag) - else if (acc.size == 0) acc.add(kDocTag) + ?.fold(mutableListOf()) { acc, kdocTag -> + if (acc.size > 0 && acc.last().knownTag != kdocTag.knownTag) { + acc.add(kdocTag) + } else if (acc.size == 0) { + acc.add(kdocTag) + } acc } - ?.map { it.knownTag }?.equals(basicTagsOrdered) + ?.map { it.knownTag } + ?.equals(basicTagsOrdered) - if (kDocTags != null && !isTagsInCorrectOrder!!) { + if (kdocTags != null && !isTagsInCorrectOrder!!) { KDOC_WRONG_TAGS_ORDER.warnAndFix(configRules, emitWarn, isFixMode, - basicTags.joinToString(", ") { "@${it.name}" }, basicTags.first().node.startOffset, basicTags.first().node) { - val kDocSection = node.getFirstChildWithType(KDOC_SECTION)!! - val basicTagChildren = kDocTags + basicTags.joinToString(", ") { "@${it.name}" }, basicTags + .first() + .node + .startOffset, basicTags.first().node) { + val kdocSection = node.getFirstChildWithType(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.clone() as CompositeElement, basicTagChildren[index]) - kDocSection.removeChild(basicTagChildren[index]) + val tagNode = kdocTags.find { it.knownTag == tag }!!.node + kdocSection.addChild(tagNode.clone() as CompositeElement, basicTagChildren[index]) + kdocSection.removeChild(basicTagChildren[index]) } } } } - @Suppress("UnsafeCallOnNullableType") + @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION") private fun checkEmptyLineBeforeBasicTags(basicTags: List) { val firstBasicTag = basicTags.firstOrNull() if (firstBasicTag != null) { - val hasContentBefore = firstBasicTag.node.allSiblings(true) + val hasContentBefore = firstBasicTag + .node + .allSiblings(true) .let { it.subList(0, it.indexOf(firstBasicTag.node)) } .any { it.elementType !in arrayOf(WHITE_SPACE, KDOC_LEADING_ASTERISK) && it.text.isNotBlank() } val previousTag = firstBasicTag.node.prevSibling { it.elementType == KDOC_TAG } val hasEmptyLineBefore = previousTag?.hasEmptyLineAfter() - ?: (firstBasicTag.node.previousAsterisk() + ?: (firstBasicTag + .node + .previousAsterisk() ?.previousAsterisk() - ?.treeNext?.elementType == WHITE_SPACE) + ?.treeNext + ?.elementType == WHITE_SPACE) if (hasContentBefore xor hasEmptyLineBefore) { KDOC_NEWLINES_BEFORE_BASIC_TAGS.warnAndFix(configRules, emitWarn, isFixMode, - "@${firstBasicTag.name!!}", firstBasicTag.node.startOffset, firstBasicTag.node) { + "@${firstBasicTag.name!!}", firstBasicTag.node.startOffset, firstBasicTag.node) { if (hasContentBefore) { if (previousTag != null) { previousTag.applyToPrevSibling(KDOC_LEADING_ASTERISK) { @@ -236,7 +255,9 @@ class KdocFormatting(private val configRules: List) : Rule("kdoc-fo private fun checkEmptyLinesBetweenBasicTags(basicTags: List) { val tagsWithRedundantEmptyLines = basicTags.dropLast(1).filterNot { tag -> val nextWhiteSpace = tag.node.nextSibling { it.elementType == WHITE_SPACE } - val noEmptyKdocLines = tag.node.getChildren(TokenSet.create(KDOC_LEADING_ASTERISK)) + val noEmptyKdocLines = tag + .node + .getChildren(TokenSet.create(KDOC_LEADING_ASTERISK)) .filter { it.treeNext == null || it.treeNext.elementType == WHITE_SPACE } .count() == 0 nextWhiteSpace?.text?.count { it == '\n' } == 1 && noEmptyKdocLines @@ -244,45 +265,51 @@ class KdocFormatting(private val configRules: List) : Rule("kdoc-fo tagsWithRedundantEmptyLines.forEach { tag -> KDOC_NO_NEWLINES_BETWEEN_BASIC_TAGS.warnAndFix(configRules, emitWarn, isFixMode, - "@${tag.name}", tag.startOffset, tag.node) { + "@${tag.name}", tag.startOffset, tag.node) { tag.node.nextSibling { it.elementType == WHITE_SPACE }?.leaveOnlyOneNewLine() // the first asterisk before tag is not included inside KDOC_TAG node // we look for the second and take its previous which should be WHITE_SPACE with newline - tag.node.getAllChildrenWithType(KDOC_LEADING_ASTERISK).firstOrNull() + tag + .node + .getAllChildrenWithType(KDOC_LEADING_ASTERISK) + .firstOrNull() ?.let { tag.node.removeRange(it.treePrev, null) } } } } - @Suppress("UnsafeCallOnNullableType") + @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION") private fun checkNewLineAfterSpecialTags(node: ASTNode) { - val presentSpecialTagNodes = node.getFirstChildWithType(KDOC_SECTION) + val presentSpecialTagNodes = node + .getFirstChildWithType(KDOC_SECTION) ?.getAllChildrenWithType(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) + 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 + specialTagNode.lastChildNode.treePrev.elementType == WHITE_SPACE && + specialTagNode.lastChildNode.treePrev.treePrev.elementType != KDOC_LEADING_ASTERISK } if (poorlyFormattedTagNodes != null && poorlyFormattedTagNodes.isNotEmpty()) { KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS.warnAndFix(configRules, emitWarn, isFixMode, - poorlyFormattedTagNodes.joinToString(", ") { "@${(it.psi as KDocTag).name!!}" }, - poorlyFormattedTagNodes.first().startOffset, node) { + poorlyFormattedTagNodes.joinToString(", ") { "@${(it.psi as KDocTag).name!!}" }, + poorlyFormattedTagNodes.first().startOffset, node) { poorlyFormattedTagNodes.forEach { node -> - while (node.lastChildNode.elementType == KDOC_LEADING_ASTERISK - && node.lastChildNode.treePrev.treePrev.elementType == KDOC_LEADING_ASTERISK) { + 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 + 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) } @@ -294,9 +321,8 @@ class KdocFormatting(private val configRules: List) : Rule("kdoc-fo // fixme this method can be improved and extracted to utils private fun ASTNode.hasEmptyLineAfter(): Boolean { require(this.elementType == KDOC_TAG) { "This check is only for KDOC_TAG" } - return lastChildNode.elementType == KDOC_LEADING_ASTERISK - && (treeNext == null || treeNext.elementType == WHITE_SPACE - && treeNext.text.count { it == '\n' } == 1) + return lastChildNode.elementType == KDOC_LEADING_ASTERISK && + (treeNext == null || treeNext.elementType == WHITE_SPACE && treeNext.text.count { it == '\n' } == 1) } private fun ASTNode.kDocBasicTags() = kDocTags()?.filter { basicTagsList.contains(it.knownTag) } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocMethods.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocMethods.kt index cd19935f7d..418b1a47a9 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocMethods.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocMethods.kt @@ -1,6 +1,33 @@ package org.cqfn.diktat.ruleset.rules.kdoc -import com.pinterest.ktlint.core.KtLint.FILE_PATH_USER_DATA_KEY +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.common.config.rules.getCommonConfiguration +import org.cqfn.diktat.ruleset.constants.EmitType +import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_TRIVIAL_KDOC_ON_FUNCTION +import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_WITHOUT_PARAM_TAG +import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_WITHOUT_RETURN_TAG +import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_WITHOUT_THROWS_TAG +import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_ON_FUNCTION +import org.cqfn.diktat.ruleset.utils.KotlinParser +import org.cqfn.diktat.ruleset.utils.appendNewlineMergingWhiteSpace +import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.getBodyLines +import org.cqfn.diktat.ruleset.utils.getFileName +import org.cqfn.diktat.ruleset.utils.getFirstChildWithType +import org.cqfn.diktat.ruleset.utils.getIdentifierName +import org.cqfn.diktat.ruleset.utils.getRootNode +import org.cqfn.diktat.ruleset.utils.hasChildOfType +import org.cqfn.diktat.ruleset.utils.hasKnownKDocTag +import org.cqfn.diktat.ruleset.utils.hasTestAnnotation +import org.cqfn.diktat.ruleset.utils.insertTagBefore +import org.cqfn.diktat.ruleset.utils.isAccessibleOutside +import org.cqfn.diktat.ruleset.utils.isGetterOrSetter +import org.cqfn.diktat.ruleset.utils.isLocatedInTest +import org.cqfn.diktat.ruleset.utils.isStandardMethod +import org.cqfn.diktat.ruleset.utils.kDocTags +import org.cqfn.diktat.ruleset.utils.parameterNames +import org.cqfn.diktat.ruleset.utils.splitPathToDirs + import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.ast.ElementType.BINARY_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.BLOCK @@ -19,14 +46,6 @@ import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.THROW import com.pinterest.ktlint.core.ast.ElementType.TYPE_REFERENCE import com.pinterest.ktlint.core.ast.ElementType.WHEN_CONDITION_WITH_EXPRESSION -import org.cqfn.diktat.common.config.rules.RulesConfig -import org.cqfn.diktat.common.config.rules.getCommonConfiguration -import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_ON_FUNCTION -import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_TRIVIAL_KDOC_ON_FUNCTION -import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_WITHOUT_PARAM_TAG -import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_WITHOUT_RETURN_TAG -import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_WITHOUT_THROWS_TAG -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 @@ -34,7 +53,6 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag import org.jetbrains.kotlin.psi.KtThrowExpression -import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.psi.psiUtil.referenceExpression /** @@ -46,20 +64,17 @@ import org.jetbrains.kotlin.psi.psiUtil.referenceExpression */ @Suppress("ForbiddenComment") class KdocMethods(private val configRules: List) : Rule("kdoc-methods") { - companion object { - // expression body of function can have a lot of 'ElementType's, this list might be not full - private val expressionBodyTypes = setOf(BINARY_EXPRESSION, CALL_EXPRESSION, LAMBDA_EXPRESSION, REFERENCE_EXPRESSION, - CALLABLE_REFERENCE_EXPRESSION, SAFE_ACCESS_EXPRESSION, WHEN_CONDITION_WITH_EXPRESSION, COLLECTION_LITERAL_EXPRESSION) - - private val uselessKdocRegex = """^([rR]eturn|[gGsS]et)[s]?\s+\w+(\s+\w+)?$""".toRegex() - } - - private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) private var isFixMode: Boolean = false + private lateinit var emitWarn: EmitType + /** + * @param node + * @param autoCorrect + * @param emit + */ override fun visit(node: ASTNode, autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { + emit: EmitType) { isFixMode = autoCorrect emitWarn = emit @@ -75,61 +90,69 @@ class KdocMethods(private val configRules: List) : Rule("kdoc-metho } } - @Suppress("UnsafeCallOnNullableType") + @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION") private fun checkSignatureDescription(node: ASTNode) { - val kDoc = node.getFirstChildWithType(KDOC) - val kDocTags = kDoc?.kDocTags() + val kdoc = node.getFirstChildWithType(KDOC) + val kdocTags = kdoc?.kDocTags() val name = node.getIdentifierName()!!.text - val (missingParameters, kDocMissingParameters) = getMissingParameters(node, kDocTags) + val (missingParameters, kDocMissingParameters) = getMissingParameters(node, kdocTags) val explicitlyThrownExceptions = getExplicitlyThrownExceptions(node) val missingExceptions = explicitlyThrownExceptions - .minus(kDocTags + .minus(kdocTags ?.filter { it.knownTag == KDocKnownTag.THROWS } ?.mapNotNull { it.getSubjectName() } ?.toSet() ?: setOf() ) val paramCheckFailed = (missingParameters.isNotEmpty() && !node.isSingleLineGetterOrSetter()) || kDocMissingParameters.isNotEmpty() - val returnCheckFailed = checkReturnCheckFailed(node, kDocTags) + val returnCheckFailed = hasReturnCheckFailed(node, kdocTags) val throwsCheckFailed = missingExceptions.isNotEmpty() - if (paramCheckFailed) handleParamCheck(node, kDoc, missingParameters, kDocMissingParameters, kDocTags) - if (returnCheckFailed) handleReturnCheck(node, kDoc, kDocTags) - if (throwsCheckFailed) handleThrowsCheck(node, kDoc, missingExceptions) + if (paramCheckFailed) { + handleParamCheck(node, kdoc, missingParameters, kDocMissingParameters, kdocTags) + } + if (returnCheckFailed) { + handleReturnCheck(node, kdoc, kdocTags) + } + if (throwsCheckFailed) { + handleThrowsCheck(node, kdoc, missingExceptions) + } // if no tag failed, we have too little information to suggest KDoc - it would just be empty val anyTagFailed = paramCheckFailed || returnCheckFailed || throwsCheckFailed - if (kDoc == null && anyTagFailed) { + if (kdoc == null && anyTagFailed) { addKdocTemplate(node, name, missingParameters, explicitlyThrownExceptions, returnCheckFailed) - } else if (kDoc == null) { + } else if (kdoc == null) { MISSING_KDOC_ON_FUNCTION.warn(configRules, emitWarn, false, name, node.startOffset, node) } } - private fun getMissingParameters(node: ASTNode, kDocTags: Collection?): Pair, List> { + @Suppress("TYPE_ALIAS") + private fun getMissingParameters(node: ASTNode, kdocTags: Collection?): Pair, List> { val parameterNames = node.parameterNames() - val kDocParamList = kDocTags?.filter { it.knownTag == KDocKnownTag.PARAM && it.getSubjectName() != null } + val kdocParamList = kdocTags?.filter { it.knownTag == KDocKnownTag.PARAM && it.getSubjectName() != null } return if (parameterNames.isEmpty()) { - Pair(emptyList(), kDocParamList ?: listOf()) - } else if (kDocParamList != null && kDocParamList.isNotEmpty()) { - Pair(parameterNames.minus(kDocParamList.map { it.getSubjectName() }), kDocParamList.filter { it.getSubjectName() !in parameterNames }) + Pair(emptyList(), kdocParamList ?: listOf()) + } else if (kdocParamList != null && kdocParamList.isNotEmpty()) { + Pair(parameterNames.minus(kdocParamList.map { it.getSubjectName() }), kdocParamList.filter { it.getSubjectName() !in parameterNames }) } else { Pair(parameterNames.toList(), listOf()) } } - private fun checkReturnCheckFailed(node: ASTNode, kDocTags: Collection?): Boolean { - if (node.isSingleLineGetterOrSetter()) return false + private fun hasReturnCheckFailed(node: ASTNode, kdocTags: Collection?): Boolean { + if (node.isSingleLineGetterOrSetter()) { + return false + } val explicitReturnType = node.getFirstChildWithType(TYPE_REFERENCE) val hasExplicitNotUnitReturnType = explicitReturnType != null && explicitReturnType.text != "Unit" val hasExplicitUnitReturnType = explicitReturnType != null && explicitReturnType.text == "Unit" val isFunWithExpressionBody = expressionBodyTypes.any { node.hasChildOfType(it) } - val hasReturnKDoc = kDocTags != null && kDocTags.hasKnownKDocTag(KDocKnownTag.RETURN) - return (hasExplicitNotUnitReturnType || isFunWithExpressionBody && !hasExplicitUnitReturnType) - && !hasReturnKDoc + val hasReturnKdoc = kdocTags != null && kdocTags.hasKnownKDocTag(KDocKnownTag.RETURN) + return (hasExplicitNotUnitReturnType || isFunWithExpressionBody && !hasExplicitUnitReturnType) && !hasReturnKdoc } private fun getExplicitlyThrownExceptions(node: ASTNode): Set { @@ -144,12 +167,11 @@ class KdocMethods(private val configRules: List) : Rule("kdoc-metho @Suppress("UnsafeCallOnNullableType") private fun handleParamCheck(node: ASTNode, - kDoc: ASTNode?, + kdoc: ASTNode?, missingParameters: Collection, - kDocMissingParameters: List, - kDocTags: Collection?) { - - kDocMissingParameters.forEach { + kdocMissingParameters: List, + kdocTags: Collection?) { + kdocMissingParameters.forEach { KDOC_WITHOUT_PARAM_TAG.warn(configRules, emitWarn, false, "${it.getSubjectName()} param isn't present in argument list", it.node.startOffset, it.node) @@ -157,10 +179,10 @@ class KdocMethods(private val configRules: List) : Rule("kdoc-metho if (missingParameters.isNotEmpty()) { KDOC_WITHOUT_PARAM_TAG.warnAndFix(configRules, emitWarn, isFixMode, "${node.getIdentifierName()!!.text} (${missingParameters.joinToString()})", node.startOffset, node) { - val beforeTag = kDocTags?.find { it.knownTag == KDocKnownTag.RETURN } - ?: kDocTags?.find { it.knownTag == KDocKnownTag.THROWS } + val beforeTag = kdocTags?.find { it.knownTag == KDocKnownTag.RETURN } + ?: kdocTags?.find { it.knownTag == KDocKnownTag.THROWS } missingParameters.forEach { - kDoc?.insertTagBefore(beforeTag?.node) { + kdoc?.insertTagBefore(beforeTag?.node) { addChild(LeafPsiElement(KDOC_TAG_NAME, "@param")) addChild(PsiWhiteSpaceImpl(" ")) addChild(LeafPsiElement(KDOC_TEXT, it)) @@ -172,13 +194,13 @@ class KdocMethods(private val configRules: List) : Rule("kdoc-metho @Suppress("UnsafeCallOnNullableType") private fun handleReturnCheck(node: ASTNode, - kDoc: ASTNode?, - kDocTags: Collection? + kdoc: ASTNode?, + kdocTags: Collection? ) { KDOC_WITHOUT_RETURN_TAG.warnAndFix(configRules, emitWarn, isFixMode, node.getIdentifierName()!!.text, node.startOffset, node) { - val beforeTag = kDocTags?.find { it.knownTag == KDocKnownTag.THROWS } - kDoc?.insertTagBefore(beforeTag?.node) { + val beforeTag = kdocTags?.find { it.knownTag == KDocKnownTag.THROWS } + kdoc?.insertTagBefore(beforeTag?.node) { addChild(LeafPsiElement(KDOC_TAG_NAME, "@return")) } } @@ -186,13 +208,13 @@ class KdocMethods(private val configRules: List) : Rule("kdoc-metho @Suppress("UnsafeCallOnNullableType") private fun handleThrowsCheck(node: ASTNode, - kDoc: ASTNode?, + kdoc: ASTNode?, missingExceptions: Collection ) { KDOC_WITHOUT_THROWS_TAG.warnAndFix(configRules, emitWarn, isFixMode, "${node.getIdentifierName()!!.text} (${missingExceptions.joinToString()})", node.startOffset, node) { missingExceptions.forEach { - kDoc?.insertTagBefore(null) { + kdoc?.insertTagBefore(null) { addChild(LeafPsiElement(KDOC_TAG_NAME, "@throws")) addChild(LeafPsiElement(KDOC_TEXT, " ")) addChild(LeafPsiElement(KDOC_TEXT, it)) @@ -209,13 +231,13 @@ class KdocMethods(private val configRules: List) : Rule("kdoc-metho returnCheckFailed: Boolean ) { MISSING_KDOC_ON_FUNCTION.warnAndFix(configRules, emitWarn, isFixMode, name, node.startOffset, node) { - val kDocTemplate = "/**\n" + + val kdocTemplate = "/**\n" + (missingParameters.joinToString("") { " * @param $it\n" } + (if (returnCheckFailed) " * @return\n" else "") + explicitlyThrownExceptions.joinToString("") { " * @throws $it\n" } + " */\n" - ) - val kdocNode = KotlinParser().createNode(kDocTemplate).findChildByType(KDOC)!! + ) + val kdocNode = KotlinParser().createNode(kdocTemplate).findChildByType(KDOC)!! node.appendNewlineMergingWhiteSpace(node.firstChildNode, node.firstChildNode) node.addChild(kdocNode, node.firstChildNode) } @@ -224,7 +246,10 @@ class KdocMethods(private val configRules: List) : Rule("kdoc-metho private fun checkKdocBody(node: ASTNode) { val kdocTextNodes = node.getChildren(TokenSet.create(KDOC_TEXT)) if (kdocTextNodes.size == 1) { - val kdocText = kdocTextNodes.first().text.trim() + val kdocText = kdocTextNodes + .first() + .text + .trim() if (kdocText.matches(uselessKdocRegex)) { KDOC_TRIVIAL_KDOC_ON_FUNCTION.warn(configRules, emitWarn, isFixMode, kdocText, kdocTextNodes.first().startOffset, node) } @@ -232,4 +257,11 @@ class KdocMethods(private val configRules: List) : Rule("kdoc-metho } private fun ASTNode.isSingleLineGetterOrSetter() = isGetterOrSetter() && (expressionBodyTypes.any { hasChildOfType(it) } || getBodyLines().size == 1) + + companion object { + // expression body of function can have a lot of 'ElementType's, this list might be not full + private val expressionBodyTypes = setOf(BINARY_EXPRESSION, CALL_EXPRESSION, LAMBDA_EXPRESSION, REFERENCE_EXPRESSION, + CALLABLE_REFERENCE_EXPRESSION, SAFE_ACCESS_EXPRESSION, WHEN_CONDITION_WITH_EXPRESSION, COLLECTION_LITERAL_EXPRESSION) + private val uselessKdocRegex = """^([rR]eturn|[gGsS]et)[s]?\s+\w+(\s+\w+)?$""".toRegex() + } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/EnumValueCaseTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/EnumValueCaseTest.kt index 3a88b2c5f6..e4bbce6484 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/EnumValueCaseTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/EnumValueCaseTest.kt @@ -1,11 +1,12 @@ package org.cqfn.diktat.ruleset.chapter1 -import generated.WarningNames import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.getRuleConfig import org.cqfn.diktat.ruleset.constants.Warnings import org.cqfn.diktat.ruleset.rules.IdentifierNaming import org.cqfn.diktat.util.FixTestBase + +import generated.WarningNames import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -15,12 +16,10 @@ class EnumValueCaseTest : FixTestBase("test/paragraph1/naming", ::IdentifierNami RulesConfig(Warnings.ENUM_VALUE.name, true, mapOf("enumStyle" to "snakeCase")) ) - private val rulesConfigPascalCaseEnum: List = listOf( RulesConfig(Warnings.ENUM_VALUE.name, true, mapOf("enumStyle" to "pascalCase")) ) - private val rulesConfigEnumUnknownStyle: List = listOf( RulesConfig(Warnings.ENUM_VALUE.name, true, mapOf("enumStyle" to "otherCase")) diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingFixTest.kt index c52f1b47fd..a687659fb0 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingFixTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingFixTest.kt @@ -1,8 +1,9 @@ package org.cqfn.diktat.ruleset.chapter1 +import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.rules.IdentifierNaming import org.cqfn.diktat.util.FixTestBase -import org.cqfn.diktat.common.config.rules.RulesConfig + import generated.WarningNames import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingWarnTest.kt index 4a545c1faf..632627527e 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingWarnTest.kt @@ -1,9 +1,6 @@ package org.cqfn.diktat.ruleset.chapter1 -import com.pinterest.ktlint.core.LintError -import generated.WarningNames import org.cqfn.diktat.common.config.rules.RulesConfig -import org.cqfn.diktat.ruleset.constants.Warnings import org.cqfn.diktat.ruleset.constants.Warnings.BACKTICKS_PROHIBITED import org.cqfn.diktat.ruleset.constants.Warnings.CLASS_NAME_INCORRECT import org.cqfn.diktat.ruleset.constants.Warnings.CONFUSING_IDENTIFIER_NAMING @@ -20,12 +17,14 @@ import org.cqfn.diktat.ruleset.constants.Warnings.VARIABLE_NAME_INCORRECT_FORMAT import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID import org.cqfn.diktat.ruleset.rules.IdentifierNaming import org.cqfn.diktat.util.LintTestBase + +import com.pinterest.ktlint.core.LintError +import generated.WarningNames import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Tags import org.junit.jupiter.api.Test class IdentifierNamingWarnTest : LintTestBase(::IdentifierNaming) { - private val ruleId: String = "$DIKTAT_RULE_SET_ID:identifier-naming" // ======== checks for generics ======== @@ -85,7 +84,6 @@ class IdentifierNamingWarnTest : LintTestBase(::IdentifierNaming) { ) } - @Test @Tag(WarningNames.GENERIC_NAME) fun `generic method - single capital letter, can be followed by a number (check)`() { @@ -155,7 +153,7 @@ class IdentifierNamingWarnTest : LintTestBase(::IdentifierNaming) { } @Test - @Tags(Tag(WarningNames.IDENTIFIER_LENGTH),Tag(WarningNames.VARIABLE_NAME_INCORRECT)) + @Tags(Tag(WarningNames.IDENTIFIER_LENGTH), Tag(WarningNames.VARIABLE_NAME_INCORRECT)) fun `check variable length (check - negative)`() { val code = """ @@ -174,7 +172,6 @@ class IdentifierNamingWarnTest : LintTestBase(::IdentifierNaming) { ) } - @Test @Tag(WarningNames.VARIABLE_NAME_INCORRECT_FORMAT) fun `check value parameters in dataclasses (check - negative)`() { @@ -222,16 +219,16 @@ class IdentifierNamingWarnTest : LintTestBase(::IdentifierNaming) { @Test @Tags(Tag(WarningNames.ENUM_VALUE), Tag(WarningNames.CLASS_NAME_INCORRECT)) fun `check case for pascal case enum values (check - negative)`() { + val rulesConfigPascalCaseEnum: List = listOf( + RulesConfig(ENUM_VALUE.name, true, + mapOf("enumStyle" to "pascalCase")) + ) val code = - """ + """ enum class TEST_ONE { first_value, secondValue, thirdVALUE, FOURTH_VALUE } """.trimIndent() - val rulesConfigPascalCaseEnum: List = listOf( - RulesConfig(ENUM_VALUE.name, true, - mapOf("enumStyle" to "pascalCase")) - ) lintMethod(code, LintError(1, 12, ruleId, "${CLASS_NAME_INCORRECT.warnText()} TEST_ONE", true), LintError(2, 3, ruleId, "${ENUM_VALUE.warnText()} first_value", true), @@ -532,7 +529,7 @@ class IdentifierNamingWarnTest : LintTestBase(::IdentifierNaming) { @Tag(WarningNames.BACKTICKS_PROHIBITED) fun `regression - backticks should be forbidden only in declarations`() { lintMethod( - """ + """ |fun foo() { | it.assertThat(actual.detail).`as`("Detailed message").isEqualTo(expected.detail) |} @@ -587,12 +584,12 @@ class IdentifierNamingWarnTest : LintTestBase(::IdentifierNaming) { lintMethod(code, LintError(2, 9, ruleId, "${CONFUSING_IDENTIFIER_NAMING.warnText()} better name is: obj, dgt", false), - LintError(2,9, ruleId, "${VARIABLE_NAME_INCORRECT_FORMAT.warnText()} D", true), - LintError(2,9, ruleId, "${IDENTIFIER_LENGTH.warnText()} D", false), + LintError(2, 9, ruleId, "${VARIABLE_NAME_INCORRECT_FORMAT.warnText()} D", true), + LintError(2, 9, ruleId, "${IDENTIFIER_LENGTH.warnText()} D", false), LintError(3, 9, ruleId, "${CONFUSING_IDENTIFIER_NAMING.warnText()} better name is: n1, n2", false), - LintError(3,9, ruleId, "${VARIABLE_NAME_INCORRECT_FORMAT.warnText()} Z", true), - LintError(3,9, ruleId, "${IDENTIFIER_LENGTH.warnText()} Z", false), + LintError(3, 9, ruleId, "${VARIABLE_NAME_INCORRECT_FORMAT.warnText()} Z", true), + LintError(3, 9, ruleId, "${IDENTIFIER_LENGTH.warnText()} Z", false), LintError(7, 5, ruleId, "${CONFUSING_IDENTIFIER_NAMING.warnText()} better name is: bt, nxt", false), - LintError(7,5, ruleId, "${IDENTIFIER_LENGTH.warnText()} B", false)) + LintError(7, 5, ruleId, "${IDENTIFIER_LENGTH.warnText()} B", false)) } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/MethodNamingWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/MethodNamingWarnTest.kt index cd4e5689ff..278e48a03e 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/MethodNamingWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/MethodNamingWarnTest.kt @@ -1,24 +1,24 @@ package org.cqfn.diktat.ruleset.chapter1 -import com.pinterest.ktlint.core.LintError -import generated.WarningNames import org.cqfn.diktat.ruleset.constants.Warnings.FUNCTION_BOOLEAN_PREFIX import org.cqfn.diktat.ruleset.constants.Warnings.FUNCTION_NAME_INCORRECT_CASE -import org.junit.jupiter.api.Test import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID import org.cqfn.diktat.ruleset.rules.IdentifierNaming import org.cqfn.diktat.util.LintTestBase + +import com.pinterest.ktlint.core.LintError +import generated.WarningNames import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test class MethodNamingWarnTest : LintTestBase(::IdentifierNaming) { - private val ruleId: String = "$DIKTAT_RULE_SET_ID:identifier-naming" @Test @Tag(WarningNames.FUNCTION_NAME_INCORRECT_CASE) fun `method name incorrect, part 1`() { val code = - """ + """ class SomeClass { fun /* */ methODTREE(): String { @@ -32,7 +32,7 @@ class MethodNamingWarnTest : LintTestBase(::IdentifierNaming) { @Tag(WarningNames.FUNCTION_NAME_INCORRECT_CASE) fun `method name incorrect, part 2`() { val code = - """ + """ class TestPackageName { fun method_two(): String { return "" @@ -46,7 +46,7 @@ class MethodNamingWarnTest : LintTestBase(::IdentifierNaming) { @Tag(WarningNames.FUNCTION_NAME_INCORRECT_CASE) fun `method name incorrect, part 3`() { val code = - """ + """ fun String.methODTREE(): String { fun TEST(): Unit { return "" @@ -63,7 +63,7 @@ class MethodNamingWarnTest : LintTestBase(::IdentifierNaming) { @Tag(WarningNames.FUNCTION_NAME_INCORRECT_CASE) fun `method name incorrect, part 4`() { val code = - """ + """ class TestPackageName { fun methODTREE(): String { } @@ -76,7 +76,7 @@ class MethodNamingWarnTest : LintTestBase(::IdentifierNaming) { @Tag(WarningNames.FUNCTION_NAME_INCORRECT_CASE) fun `method name incorrect, part 5`() { val code = - """ + """ class TestPackageName { fun methODTREE() { } @@ -89,7 +89,7 @@ class MethodNamingWarnTest : LintTestBase(::IdentifierNaming) { @Tag(WarningNames.FUNCTION_BOOLEAN_PREFIX) fun `boolean method name incorrect`() { val code = - """ + """ fun someBooleanCheck(): Boolean { return false } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackageNamingFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackageNamingFixTest.kt index e4f50b59b2..60809b5dc2 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackageNamingFixTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackageNamingFixTest.kt @@ -1,17 +1,18 @@ package org.cqfn.diktat.ruleset.chapter1 import org.cqfn.diktat.common.config.rules.RulesConfig -import generated.WarningNames import org.cqfn.diktat.ruleset.constants.Warnings.PACKAGE_NAME_MISSING import org.cqfn.diktat.ruleset.rules.PackageNaming import org.cqfn.diktat.util.FixTestBase + +import generated.WarningNames import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test class PackageNamingFixTest : FixTestBase( - "test/paragraph1/naming/package", - ::PackageNaming, - listOf(RulesConfig("DIKTAT_COMMON", true, mapOf("domainName" to "org.cqfn.diktat"))) + "test/paragraph1/naming/package", + ::PackageNaming, + listOf(RulesConfig("DIKTAT_COMMON", true, mapOf("domainName" to "org.cqfn.diktat"))) ) { @Test @Tag(WarningNames.PACKAGE_NAME_INCORRECT_CASE) diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackageNamingWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackageNamingWarnTest.kt index 69a258bad6..3b63be1189 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackageNamingWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackageNamingWarnTest.kt @@ -1,20 +1,19 @@ package org.cqfn.diktat.ruleset.chapter1 -import com.pinterest.ktlint.core.LintError import org.cqfn.diktat.common.config.rules.RulesConfig -import generated.WarningNames -import org.junit.jupiter.api.Test -import org.cqfn.diktat.ruleset.rules.PackageNaming import org.cqfn.diktat.ruleset.constants.Warnings.* import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.ruleset.rules.PackageNaming import org.cqfn.diktat.util.LintTestBase import org.cqfn.diktat.util.testFileName + +import com.pinterest.ktlint.core.LintError +import generated.WarningNames import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test class PackageNamingWarnTest : LintTestBase(::PackageNaming) { - private val ruleId: String = "$DIKTAT_RULE_SET_ID:package-naming" - private val rulesConfigList: List = listOf( RulesConfig("DIKTAT_COMMON", true, mapOf("domainName" to "org.cqfn.diktat")) ) diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackagePathFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackagePathFixTest.kt index 24c575a84c..59e1920d7e 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackagePathFixTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackagePathFixTest.kt @@ -1,19 +1,19 @@ package org.cqfn.diktat.ruleset.chapter1 import org.cqfn.diktat.common.config.rules.RulesConfig -import generated.WarningNames import org.cqfn.diktat.ruleset.constants.Warnings import org.cqfn.diktat.ruleset.rules.PackageNaming import org.cqfn.diktat.util.FixTestBase + +import generated.WarningNames import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test class PackagePathFixTest : FixTestBase( - "test/paragraph1/naming/package/src/main/kotlin", - ::PackageNaming, - listOf(RulesConfig("DIKTAT_COMMON", true, mapOf("domainName" to "org.cqfn.diktat"))) + "test/paragraph1/naming/package/src/main/kotlin", + ::PackageNaming, + listOf(RulesConfig("DIKTAT_COMMON", true, mapOf("domainName" to "org.cqfn.diktat"))) ) { - @Test @Tag(WarningNames.PACKAGE_NAME_INCORRECT_PATH) fun `fixing package name that differs from a path`() { diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/ASTNodeUtilsTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtilsTest.kt similarity index 95% rename from diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/ASTNodeUtilsTest.kt rename to diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtilsTest.kt index 2178ad07b4..7021b17966 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/ASTNodeUtilsTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtilsTest.kt @@ -1,5 +1,8 @@ package org.cqfn.diktat.ruleset.utils +import org.cqfn.diktat.ruleset.constants.EmitType +import org.cqfn.diktat.util.applyToCode + import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.RuleSet @@ -19,7 +22,6 @@ import com.pinterest.ktlint.core.ast.ElementType.VAL_KEYWORD import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE import com.pinterest.ktlint.core.ast.isLeaf import com.pinterest.ktlint.core.ast.nextSibling -import org.cqfn.diktat.util.applyToCode import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType @@ -28,8 +30,7 @@ import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test @Suppress("LargeClass", "UnsafeCallOnNullableType") -class ASTNodeUtilsTest { - +class AstNodeUtilsTest { @Test fun `String representation of ASTNode`() { val code = """ @@ -291,8 +292,9 @@ class ASTNodeUtilsTest { applyToCode(code, 0) { node, _ -> val setParent = if (node.treeParent != null) { node.treeParent.getChildren(null).toSet() - } else + } else { setOf(node) + } val setSibling = node.allSiblings(true).toSet() Assertions.assertEquals(setParent, setSibling) Assertions.assertTrue(setParent.isNotEmpty()) @@ -301,26 +303,22 @@ class ASTNodeUtilsTest { @Test fun `regression - check for companion object`() { - var code = """ + applyToCode(""" object Test { val id = 1 } - """.trimIndent() - - applyToCode(code, 1) { node, counter -> + """.trimIndent(), 1) { node, counter -> if (node.elementType == PROPERTY) { Assertions.assertFalse(node.isNodeFromCompanionObject()) counter.incrementAndGet() } } - code = """ + applyToCode(""" companion object Test { val id = 1 } - """.trimIndent() - - applyToCode(code, 1) { node, counter -> + """.trimIndent(), 1) { node, counter -> if (node.elementType == PROPERTY) { Assertions.assertTrue(node.isNodeFromCompanionObject()) counter.incrementAndGet() @@ -401,11 +399,12 @@ class ASTNodeUtilsTest { """.trimIndent() applyToCode(code, 8) { node, counter -> - if (node.elementType != FILE) + if (node.elementType != FILE) { node.getChildren(null).forEach { Assertions.assertFalse(it.isNodeFromFileLevel()) counter.incrementAndGet() } + } } } @@ -425,8 +424,9 @@ class ASTNodeUtilsTest { """.trimIndent() var isVal = false applyToCode(code, 0) { node, _ -> - if (node.isValProperty()) + if (node.isValProperty()) { isVal = true + } } Assertions.assertTrue(isVal) } @@ -447,8 +447,9 @@ class ASTNodeUtilsTest { """.trimIndent() var isConst = false applyToCode(code, 0) { node, _ -> - if (node.isConst()) + if (node.isConst()) { isConst = true + } } Assertions.assertTrue(isConst) } @@ -469,8 +470,9 @@ class ASTNodeUtilsTest { """.trimIndent() var isVar = false applyToCode(code, 0) { node, _ -> - if (node.isVarProperty()) + if (node.isVarProperty()) { isVar = true + } } Assertions.assertTrue(isVar) } @@ -487,11 +489,12 @@ class ASTNodeUtilsTest { } """.trimIndent() val list = mutableListOf() - val leafWithTypeList = mutableListOf() + val leafWithTypeList: MutableList = mutableListOf() var firstNode: ASTNode? = null applyToCode(code, 0) { node, _ -> - if (firstNode == null) + if (firstNode == null) { firstNode = node + } if (node.isLeaf() && node.elementType == WHITE_SPACE) { leafWithTypeList.add(node) } @@ -515,8 +518,9 @@ class ASTNodeUtilsTest { var firstNode: ASTNode? = null var resultNode: ASTNode? = null applyToCode(code, 0) { node, _ -> - if (firstNode == null) + if (firstNode == null) { firstNode = node + } if (resultNode == null && node.elementType == CLASS_BODY) { resultNode = node } @@ -537,10 +541,11 @@ class ASTNodeUtilsTest { } """.trimIndent() var firstNode: ASTNode? = null - val listResults = mutableListOf() + val listResults: MutableList = mutableListOf() applyToCode(code, 0) { node, _ -> - if (firstNode == null) + if (firstNode == null) { firstNode = node + } if (node.elementType == IDENTIFIER) { listResults.add(node) } @@ -625,7 +630,10 @@ class ASTNodeUtilsTest { val parent = node.treeParent val firstText = node.text node.leaveOnlyOneNewLine() - val secondText = parent.getChildren(null).last().text + val secondText = parent + .getChildren(null) + .last() + .text Assertions.assertEquals("\n", secondText) Assertions.assertEquals("\n\n", firstText) counter.incrementAndGet() @@ -721,8 +729,23 @@ private class PrettyPrintingVisitor(private val elementType: IElementType, private val level: Int, private val maxLevel: Int, private val expected: String) : Rule("print-ast") { + override fun visit(node: ASTNode, + autoCorrect: Boolean, + emit: EmitType) { + if (node.elementType == elementType) { + Assertions.assertEquals( + expected.replace("\n", System.lineSeparator()), + node.prettyPrint(level, maxLevel) + ) + } + } companion object { - fun assertStringRepr(elementType: IElementType, code: String, level: Int = 0, maxLevel: Int = -1, expected: String) { + fun assertStringRepr( + elementType: IElementType, + code: String, + level: Int = 0, + maxLevel: Int = -1, + expected: String) { KtLint.lint( KtLint.Params( text = code, @@ -732,15 +755,4 @@ private class PrettyPrintingVisitor(private val elementType: IElementType, ) } } - - override fun visit(node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { - if (node.elementType == elementType) { - Assertions.assertEquals( - expected.replace("\n", System.lineSeparator()), - node.prettyPrint(level, maxLevel) - ) - } - } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AvailableRulesDocTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AvailableRulesDocTest.kt index 5f31ad06ea..50fc433c64 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AvailableRulesDocTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AvailableRulesDocTest.kt @@ -10,10 +10,20 @@ import java.io.File * Documentation should be added to available-rules.md */ class AvailableRulesDocTest { - companion object { - const val availableRulesFile = "../info/available-rules.md" - const val tableDelimiter = "-----" - const val ruleNameHeader = "Rule name" + private fun getAllRulesFromDoc(): List { + val listWithRulesFromDoc: MutableList = mutableListOf() + File(AVAILABLE_RULES_FILE).forEachLine { line -> + val splitMarkDown = line + .split("|") + + val ruleName = splitMarkDown.get(3).trim() + + if (!ruleName.startsWith(TABLE_DELIMITER) && + !ruleName.startsWith(RULE_NAME_HEADER)) { + listWithRulesFromDoc.add(ruleName) + } + } + return listWithRulesFromDoc } @Test @@ -28,9 +38,9 @@ class AvailableRulesDocTest { val docs = "| | | $ruleName" + "| | | | |" """ - Cannot find warning $ruleName in $availableRulesFile. - You can fix it by adding the following description below with more info to $availableRulesFile: - add $docs to $availableRulesFile + Cannot find warning $ruleName in $AVAILABLE_RULES_FILE. + You can fix it by adding the following description below with more info to $AVAILABLE_RULES_FILE: + add $docs to $AVAILABLE_RULES_FILE """ } } @@ -46,19 +56,9 @@ class AvailableRulesDocTest { } } - private fun getAllRulesFromDoc(): List { - val listWithRulesFromDoc = mutableListOf() - File(availableRulesFile).forEachLine { line -> - val splitMarkDown = line - .split("|") - - val ruleName = splitMarkDown.get(3).trim() - - if (!ruleName.startsWith(tableDelimiter) && - !ruleName.startsWith(ruleNameHeader)) { - listWithRulesFromDoc.add(ruleName) - } - } - return listWithRulesFromDoc + companion object { + const val AVAILABLE_RULES_FILE = "../info/available-rules.md" + const val RULE_NAME_HEADER = "Rule name" + const val TABLE_DELIMITER = "-----" } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/FunctionASTNodeUtilsTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/FunctionAstNodeUtilsTest.kt similarity index 96% rename from diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/FunctionASTNodeUtilsTest.kt rename to diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/FunctionAstNodeUtilsTest.kt index 752b0c29b2..666c455565 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/FunctionASTNodeUtilsTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/FunctionAstNodeUtilsTest.kt @@ -1,12 +1,13 @@ package org.cqfn.diktat.ruleset.utils -import com.pinterest.ktlint.core.ast.ElementType.FUN import org.cqfn.diktat.util.applyToCode + +import com.pinterest.ktlint.core.ast.ElementType.FUN import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test @Suppress("UnsafeCallOnNullableType") -class FunctionASTNodeUtilsTest { +class FunctionAstNodeUtilsTest { @Test fun `should detect parameters in function - no parameters`() { applyToCode("fun foo() { }", 1) { node, counter -> diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParserTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParserTest.kt index ce5af679c6..26fec37a56 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParserTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParserTest.kt @@ -1,5 +1,7 @@ package org.cqfn.diktat.ruleset.utils +import org.cqfn.diktat.util.applyToCode + import com.pinterest.ktlint.core.ast.ElementType.CLASS import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY import com.pinterest.ktlint.core.ast.ElementType.CLASS_KEYWORD @@ -13,16 +15,13 @@ import com.pinterest.ktlint.core.ast.ElementType.PACKAGE_DIRECTIVE import com.pinterest.ktlint.core.ast.ElementType.PROPERTY import com.pinterest.ktlint.core.ast.ElementType.RBRACE import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE -import org.cqfn.diktat.util.applyToCode import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl -import org.jetbrains.kotlin.psi.KtPsiFactory import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows class KotlinParserTest { - @Test fun `test simple property`() { val node = KotlinParser().createNode("val x: Int = 10") @@ -110,6 +109,12 @@ class KotlinParserTest { val function = """ |fun foo() = "Hello" """.trimMargin() + var nodeToApply: ASTNode? = null + applyToCode(emptyClass, 0) { newNode, _ -> + if (nodeToApply == null) { + nodeToApply = newNode + } + } val resultClass = """ |package org.cqfn.diktat.ruleset.utils | @@ -119,13 +124,11 @@ class KotlinParserTest { |fun foo() = "Hello" |} """.trimMargin() - var nodeToApply: ASTNode? = null var resultNode: ASTNode? = null - applyToCode(emptyClass, 0) { newNode, _ -> - if (nodeToApply == null) nodeToApply = newNode - } applyToCode(resultClass, 0) { newNode, _ -> - if (resultNode == null) resultNode = newNode + if (resultNode == null) { + resultNode = newNode + } } Assertions.assertTrue(nodeToApply!!.prettyPrint() == KotlinParser().createNode(emptyClass, true).prettyPrint()) val classNode = nodeToApply!!.findChildByType(CLASS)!!.findChildByType(CLASS_BODY)!! @@ -192,13 +195,13 @@ class KotlinParserTest { |*/ |fun foo() """.trimMargin() - val KDocText = """ + val kdocText = """ /** * [link]haha */ """.trimIndent() val node = KotlinParser().createNode(code) Assertions.assertEquals(KDOC, node.findChildByType(FUN)!!.firstChildNode.elementType) - Assertions.assertEquals(KDocText, node.findChildByType(FUN)!!.firstChildNode.text) + Assertions.assertEquals(kdocText, node.findChildByType(FUN)!!.firstChildNode.text) } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/RulesConfigYamlTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/RulesConfigYamlTest.kt index 704a7bab54..138d1f294e 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/RulesConfigYamlTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/RulesConfigYamlTest.kt @@ -1,21 +1,23 @@ package org.cqfn.diktat.ruleset.utils -import com.charleskorn.kaml.Yaml -import kotlinx.serialization.encodeToString import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.RulesConfigReader import org.cqfn.diktat.common.config.rules.getRuleConfig import org.cqfn.diktat.ruleset.constants.Warnings + +import com.charleskorn.kaml.Yaml import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test + import java.io.File +import kotlinx.serialization.encodeToString + /** * Special test that checks that developer has not forgotten to add his warning to a diktat-analysis.yml * This file is needed to be in tact with latest changes in Warnings.kt */ class RulesConfigYamlTest { - private val pathMap = mapOf("diktat-analysis.yml" to "diKTat/diktat-rules/src/main/resources/diktat-analysis.yml", "diktat-analysis-huawei.yml" to "diKTat/diktat-rules/src/main/resources/diktat-analysis-huawei.yml", "parent/diktat-analysis.yml" to "diKTat/diktat-analysis.yml") diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/SuppressTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/SuppressTest.kt index 981a1977c0..bb51f2630b 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/SuppressTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/SuppressTest.kt @@ -1,14 +1,14 @@ package org.cqfn.diktat.ruleset.utils -import com.pinterest.ktlint.core.LintError import org.cqfn.diktat.ruleset.constants.Warnings import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID import org.cqfn.diktat.ruleset.rules.IdentifierNaming import org.cqfn.diktat.util.LintTestBase + +import com.pinterest.ktlint.core.LintError import org.junit.jupiter.api.Test class SuppressTest : LintTestBase(::IdentifierNaming) { - private val ruleId: String = "$DIKTAT_RULE_SET_ID:identifier-naming" @Test @@ -47,7 +47,7 @@ class SuppressTest : LintTestBase(::IdentifierNaming) { | } |} """.trimMargin(), - LintError(12,14,ruleId, "${Warnings.FUNCTION_NAME_INCORRECT_CASE.warnText()} methODTREEASA", + LintError(12, 14, ruleId, "${Warnings.FUNCTION_NAME_INCORRECT_CASE.warnText()} methODTREEASA", true) ) } @@ -140,8 +140,7 @@ class SuppressTest : LintTestBase(::IdentifierNaming) { } """.trimIndent() lintMethod(code, - LintError(3,13, "$DIKTAT_RULE_SET_ID:identifier-naming", - "${Warnings.FUNCTION_NAME_INCORRECT_CASE.warnText()} methODTREE", true)) + LintError(3, 13, "$DIKTAT_RULE_SET_ID:identifier-naming", + "${Warnings.FUNCTION_NAME_INCORRECT_CASE.warnText()} methODTREE", true)) } - } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesSearchTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesSearchTest.kt index 8b61264520..0aa25b2a2f 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesSearchTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesSearchTest.kt @@ -1,9 +1,10 @@ package org.cqfn.diktat.ruleset.utils -import com.pinterest.ktlint.core.ast.ElementType import org.cqfn.diktat.ruleset.utils.search.VariablesSearch import org.cqfn.diktat.ruleset.utils.search.default import org.cqfn.diktat.util.applyToCode + +import com.pinterest.ktlint.core.ast.ElementType import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -37,10 +38,8 @@ class VariablesSearchTest { variablesSearchAbstract.collectVariables() } - assertTrue(thrown.message!!.contains("To collect all variables in a file you need to provide file root node")); - + assertTrue(thrown.message!!.contains("To collect all variables in a file you need to provide file root node")) } } } } - diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithAssignmentsSearchTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithAssignmentsSearchTest.kt index 4d7951514b..5a74de52c4 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithAssignmentsSearchTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithAssignmentsSearchTest.kt @@ -1,8 +1,9 @@ package org.cqfn.diktat.ruleset.utils -import com.pinterest.ktlint.core.ast.ElementType.FILE import org.cqfn.diktat.ruleset.utils.search.findAllVariablesWithAssignments import org.cqfn.diktat.util.applyToCode + +import com.pinterest.ktlint.core.ast.ElementType.FILE import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithUsagesSearchTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithUsagesSearchTest.kt index bafc58f04e..d0369f181c 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithUsagesSearchTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithUsagesSearchTest.kt @@ -1,8 +1,9 @@ package org.cqfn.diktat.ruleset.utils -import com.pinterest.ktlint.core.ast.ElementType.FILE import org.cqfn.diktat.ruleset.utils.search.findAllVariablesWithUsages import org.cqfn.diktat.util.applyToCode + +import com.pinterest.ktlint.core.ast.ElementType.FILE import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test @@ -83,7 +84,6 @@ class VariablesWithUsagesSearchTest { } } - @Test fun `testing proper variables search in simple class with property`() { applyToCode(""" diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt index 0b99e3254c..cc95c00b47 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt @@ -1,16 +1,17 @@ package org.cqfn.diktat.util -import com.pinterest.ktlint.core.Rule -import com.pinterest.ktlint.core.RuleSet -import com.pinterest.ktlint.core.RuleSetProvider import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.RulesConfigReader import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.RuleSet +import com.pinterest.ktlint.core.RuleSetProvider import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test + import java.io.File -import kotlin.reflect.jvm.reflect /** * simple class for emulating RuleSetProvider to inject .yml rule configuration and mock this part of code @@ -19,26 +20,27 @@ class DiktatRuleSetProvider4Test(private val ruleSupplier: (rulesConfigList: Lis rulesConfigList: List?) : RuleSetProvider { private val rulesConfigList: List? = rulesConfigList ?: RulesConfigReader(javaClass.classLoader).readResource("diktat-analysis.yml") - override fun get(): RuleSet { - return RuleSet( - DIKTAT_RULE_SET_ID, - ruleSupplier.invoke(rulesConfigList ?: emptyList()) - ) - } + override fun get() = RuleSet( + DIKTAT_RULE_SET_ID, + ruleSupplier.invoke(rulesConfigList ?: emptyList()) + ) } class DiktatRuleSetProviderTest { - - companion object { - private val IGNORE_FILE = listOf("DiktatRuleSetProvider") - } - @Suppress("UnsafeCallOnNullableType") @Test fun `check DiktatRuleSetProviderTest contain all rules`() { val path = "${System.getProperty("user.dir")}/src/main/kotlin/org/cqfn/diktat/ruleset/rules" - val filesName = File(path).walk().filter { it.isFile }.map { it.nameWithoutExtension }.filterNot { it in IGNORE_FILE } + val filesName = File(path) + .walk() + .filter { it.isFile } + .map { it.nameWithoutExtension } + .filterNot { it in ignoreFile } val rulesName = DiktatRuleSetProvider().get().map { it::class.simpleName!! } Assertions.assertEquals(filesName.sorted().toList(), rulesName.sorted()) } + + companion object { + private val ignoreFile = listOf("DiktatRuleSetProvider") + } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/FixTestBase.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/FixTestBase.kt index af530426d2..9e92f0be22 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/FixTestBase.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/FixTestBase.kt @@ -1,15 +1,18 @@ package org.cqfn.diktat.util -import com.pinterest.ktlint.core.LintError -import com.pinterest.ktlint.core.Rule -import com.pinterest.ktlint.core.RuleSetProvider import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.test.framework.processing.TestComparatorUnit + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.RuleSetProvider import org.junit.jupiter.api.Assertions +/** + * @property resourceFilePath path to files which will be compared in tests + */ open class FixTestBase(protected val resourceFilePath: String, private val ruleSetProviderRef: (rulesConfigList: List?) -> RuleSetProvider, - private val cb: (LintError, Boolean) -> Unit = defaultCallback, + private val cb: LintErrorCallback = defaultCallback, private val rulesConfigList: List? = null) { private val testComparatorUnit = TestComparatorUnit(resourceFilePath) { text, fileName -> format(ruleSetProviderRef, text, fileName, rulesConfigList, cb = cb) @@ -18,9 +21,17 @@ open class FixTestBase(protected val resourceFilePath: String, constructor(resourceFilePath: String, ruleSupplier: (rulesConfigList: List) -> Rule, rulesConfigList: List? = null, - cb: (LintError, Boolean) -> Unit = defaultCallback) : this(resourceFilePath, - { overrideRulesConfigList -> DiktatRuleSetProvider4Test(ruleSupplier, overrideRulesConfigList) }, cb, rulesConfigList) + cb: LintErrorCallback = defaultCallback) : this( + resourceFilePath, + { overrideRulesConfigList -> DiktatRuleSetProvider4Test(ruleSupplier, overrideRulesConfigList) }, + cb, + rulesConfigList + ) + /** + * @param expectedPath path to file with expected result, relative to [resourceFilePath] + * @param testPath path to file with code that will be transformed by formatter, relative to [resourceFilePath] + */ protected fun fixAndCompare(expectedPath: String, testPath: String) { Assertions.assertTrue( testComparatorUnit @@ -28,6 +39,11 @@ open class FixTestBase(protected val resourceFilePath: String, ) } + /** + * @param expectedPath path to file with expected result, relative to [resourceFilePath] + * @param testPath path to file with code that will be transformed by formatter, relative to [resourceFilePath] + * @param overrideRulesConfigList optional override to [rulesConfigList] + */ protected fun fixAndCompare(expectedPath: String, testPath: String, overrideRulesConfigList: List) { diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/LintTestBase.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/LintTestBase.kt index e4048b5950..c207f82952 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/LintTestBase.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/LintTestBase.kt @@ -1,17 +1,31 @@ package org.cqfn.diktat.util +import org.cqfn.diktat.common.config.rules.RulesConfig + import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.LintError import com.pinterest.ktlint.core.Rule -import org.cqfn.diktat.common.config.rules.RulesConfig +/** + * Base class for testing rules without fixing code. + * @property ruleSupplier mapping of list of [RulesConfig] into a [Rule] + * @property rulesConfigList optional custom rules config + */ open class LintTestBase(private val ruleSupplier: (rulesConfigList: List) -> Rule, private val rulesConfigList: List? = null) { + /** + * Perform linting of [code], collect errors and compare with [lintErrors] + * + * @param code code to check + * @param lintErrors expected errors + * @param rulesConfigList optional override for `this.rulesConfigList` + * @param fileName optional override for file name + */ fun lintMethod(code: String, vararg lintErrors: LintError, rulesConfigList: List? = null, fileName: String? = null) { - val res = mutableListOf() + val res: MutableList = mutableListOf() val actualFileName = fileName ?: testFileName KtLint.lint( KtLint.Params( @@ -19,7 +33,7 @@ open class LintTestBase(private val ruleSupplier: (rulesConfigList: List res.add(e) }, + cb = { lintError, _ -> res.add(lintError) }, userData = mapOf("file_path" to actualFileName) ) ) diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/TestUtils.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/TestUtils.kt index 908ea900ef..c8f2245eff 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/TestUtils.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/TestUtils.kt @@ -1,5 +1,9 @@ package org.cqfn.diktat.util +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.EmitType +import org.cqfn.diktat.ruleset.utils.log + import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.LintError import com.pinterest.ktlint.core.Rule @@ -7,43 +11,70 @@ import com.pinterest.ktlint.core.RuleSet import com.pinterest.ktlint.core.RuleSetProvider import org.assertj.core.api.Assertions import org.assertj.core.api.SoftAssertions -import org.cqfn.diktat.common.config.rules.RulesConfig -import org.cqfn.diktat.ruleset.utils.log import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.konan.file.File + import java.util.concurrent.atomic.AtomicInteger internal val testFileName = "${File.separator}TestFileName.kt" -internal fun List.assertEquals(vararg expectedLintErrors: LintError) = +typealias LintErrorCallback = (LintError, Boolean) -> Unit + +/** + * Compare [LintError]s from [this] with [expectedLintErrors] + * + * @param expectedLintErrors expected [LintError]s + */ +internal fun List.assertEquals(vararg expectedLintErrors: LintError) { + if (size == expectedLintErrors.size) { Assertions.assertThat(this) - .hasSize(expectedLintErrors.size) .allSatisfy { actual -> val expected = expectedLintErrors[this.indexOf(actual)] SoftAssertions.assertSoftly { - it.assertThat(actual.line).`as`("Line").isEqualTo(expected.line) - it.assertThat(actual.col).`as`("Column").isEqualTo(expected.col) - it.assertThat(actual.ruleId).`as`("Rule id").isEqualTo(expected.ruleId) - it.assertThat(actual.detail).`as`("Detailed message").isEqualTo(expected.detail) - it.assertThat(actual.canBeAutoCorrected).`as`("Can be autocorrected").isEqualTo(expected.canBeAutoCorrected) + it.assertThat(actual.line) + .`as`("Line") + .isEqualTo(expected.line) + it.assertThat(actual.col) + .`as`("Column") + .isEqualTo(expected.col) + it.assertThat(actual.ruleId) + .`as`("Rule id") + .isEqualTo(expected.ruleId) + it.assertThat(actual.detail) + .`as`("Detailed message") + .isEqualTo(expected.detail) + it.assertThat(actual.canBeAutoCorrected) + .`as`("Can be autocorrected") + .isEqualTo(expected.canBeAutoCorrected) } } + } else { + Assertions.assertThat(this).containsExactly(*expectedLintErrors) + } +} +/** + * @param ruleSetProviderRef + * @param text + * @param fileName + * @param rulesConfigList + * @param cb callback to be called on unhandled [LintError]s + * @return formatted code + */ internal fun format(ruleSetProviderRef: (rulesConfigList: List?) -> RuleSetProvider, text: String, fileName: String, rulesConfigList: List? = null, - cb: (lintError: LintError, corrected: Boolean) -> Unit = defaultCallback): String { - return KtLint.format( - KtLint.Params( - text = text, - ruleSets = listOf(ruleSetProviderRef.invoke(rulesConfigList).get()), - fileName = fileName, - cb = cb, - userData = mapOf("file_path" to fileName) - ) - ) -} + cb: LintErrorCallback = defaultCallback) = + KtLint.format( + KtLint.Params( + text = text, + ruleSets = listOf(ruleSetProviderRef.invoke(rulesConfigList).get()), + fileName = fileName, + cb = cb, + userData = mapOf("file_path" to fileName) + ) + ) internal val defaultCallback: (lintError: LintError, corrected: Boolean) -> Unit = { lintError, _ -> log.warn("Received linting error: $lintError") @@ -53,6 +84,10 @@ internal val defaultCallback: (lintError: LintError, corrected: Boolean) -> Unit * This utility function lets you run arbitrary code on every node of given [code]. * It also provides you with counter which can be incremented inside [applyToNode] and then will be compared to [expectedAsserts]. * This allows you to keep track of how many assertions have actually been run on your code during tests. + * + * @param code + * @param expectedAsserts Number of expected times of assert invocation + * @param applyToNode Function to be called on each AST node, should increment counter if assert is called */ internal fun applyToCode(code: String, expectedAsserts: Int, @@ -65,7 +100,7 @@ internal fun applyToCode(code: String, RuleSet("test", object : Rule("astnode-utils-test") { override fun visit(node: ASTNode, autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { + emit: EmitType) { applyToNode(node, counter) } }) @@ -73,5 +108,8 @@ internal fun applyToCode(code: String, cb = { _, _ -> Unit } ) ) - Assertions.assertThat(counter.get()).`as`("Number of expected asserts").isEqualTo(expectedAsserts) + Assertions + .assertThat(counter.get()) + .`as`("Number of expected asserts") + .isEqualTo(expectedAsserts) }