diff --git a/diktat-rules/src/main/kotlin/generated/WarningNames.kt b/diktat-rules/src/main/kotlin/generated/WarningNames.kt index e33aaac83b..7a5faa6fc2 100644 --- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt +++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt @@ -5,6 +5,8 @@ package generated import kotlin.String public object WarningNames { + public const val DUMMY_TEST_WARNING: String = "DUMMY_TEST_WARNING" + public const val PACKAGE_NAME_MISSING: String = "PACKAGE_NAME_MISSING" public const val PACKAGE_NAME_INCORRECT_CASE: String = "PACKAGE_NAME_INCORRECT_CASE" 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 cc115cd7ce..4d3f53052e 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 @@ -31,6 +31,9 @@ enum class Warnings( val canBeAutoCorrected: Boolean, val ruleId: String, private val warn: String) : Rule { + // ======== dummy test warning ====== + DUMMY_TEST_WARNING(true, "0.0.0", "this is a dummy warning that can be used for manual testing of fixer/checker"), + // ======== chapter 1 ======== PACKAGE_NAME_MISSING(true, "1.2.1", "no package name declared in a file"), PACKAGE_NAME_INCORRECT_CASE(true, "1.2.1", "package name should be completely in a lower case"), @@ -112,7 +115,7 @@ enum class Warnings( COMPLEX_EXPRESSION(false, "3.6.3", "complex dot qualified expression should be replaced with variable"), // FixMe: autofixing will be added for this rule - STRING_CONCATENATION(false, "3.15.1", "strings should not be concatenated using plus operator - use string templates instead if the statement fits one line"), + STRING_CONCATENATION(true, "3.15.1", "strings should not be concatenated using plus operator - use string templates instead if the statement fits one line"), TOO_MANY_BLANK_LINES(true, "3.7.1", "too many consecutive blank lines"), WRONG_WHITESPACE(true, "3.8.1", "incorrect usage of whitespaces for code separation"), TOO_MANY_CONSECUTIVE_SPACES(true, "3.8.1", "too many consecutive spaces"), diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/dummy/DummyWarning.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/dummy/DummyWarning.kt new file mode 100644 index 0000000000..d7bb77507b --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/dummy/DummyWarning.kt @@ -0,0 +1,24 @@ +package org.cqfn.diktat.ruleset.dummy + +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.rules.DiktatRule +import org.jetbrains.kotlin.com.intellij.lang.ASTNode + +/** + * Dummy warning used for testing and debug purposes. + * Can be used in manual testing. + */ +class DummyWarning(configRules: List) : DiktatRule( + "dummy-rule", + configRules, + listOf( + Warnings.FILE_NAME_INCORRECT, + Warnings.FILE_NAME_MATCH_CLASS + ) +) { + private lateinit var filePath: String + + @Suppress("EmptyFunctionBlock") + override fun logic(node: ASTNode) {} +} diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt index fdeaf9ede7..d85871023f 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt @@ -4,6 +4,7 @@ import org.cqfn.diktat.common.config.rules.DIKTAT_COMMON import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.RulesConfigReader import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.dummy.DummyWarning import org.cqfn.diktat.ruleset.rules.chapter1.FileNaming import org.cqfn.diktat.ruleset.rules.chapter1.IdentifierNaming import org.cqfn.diktat.ruleset.rules.chapter1.PackageNaming @@ -133,6 +134,9 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS // We don't have a way to enforce a specific order, so we should just be careful when adding new rules to this list and, when possible, // cover new rules in smoke test as well. If a rule needs to be at a specific position in a list, please add comment explaining it (like for NewlinesRule). val rules = listOf( + // test warning that can be used for manual testing of diktat + ::DummyWarning, + // comments & documentation ::CommentsRule, ::SingleConstructorRule, // this rule can add properties to a primary constructor, so should be before KdocComments @@ -164,7 +168,6 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS ::CheckInverseMethodRule, ::StatelessClassesRule, ::ImplicitBackingPropertyRule, - ::StringTemplateFormatRule, ::DataClassesRule, ::LocalVariablesRule, ::SmartCastRule, @@ -180,6 +183,7 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS ::AnnotationNewLineRule, ::SortRule, ::StringConcatenationRule, + ::StringTemplateFormatRule, ::AccurateCalculationsRule, ::LineLength, ::RunInScript, @@ -204,7 +208,8 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS ::FileStructureRule, // this rule should be right before indentation because it should operate on already valid code ::NewlinesRule, // newlines need to be inserted right before fixing indentation ::WhiteSpaceRule, // this rule should be after other rules that can cause wrong spacing - ::IndentationRule // indentation rule should be the last because it fixes formatting after all the changes done by previous rules + ::IndentationRule, // indentation rule should be the last because it fixes formatting after all the changes done by previous rules + ) .map { it.invoke(configRules) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/PackageNaming.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/PackageNaming.kt index 835060810d..6e22883fbd 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/PackageNaming.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/PackageNaming.kt @@ -58,7 +58,7 @@ class PackageNaming(configRules: List) : DiktatRule( } // getting all identifiers from existing package name into the list like [org, diktat, project] - val wordsInPackageName = node.findAllNodesWithSpecificType(IDENTIFIER) + val wordsInPackageName = node.findAllDescendantsWithSpecificType(IDENTIFIER) // no need to check that packageIdentifiers is empty, because in this case parsing will fail checkPackageName(wordsInPackageName, node) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/CommentsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/CommentsRule.kt index 4b60e373ed..1029eb5330 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/CommentsRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/CommentsRule.kt @@ -4,7 +4,7 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.ListOfPairs import org.cqfn.diktat.ruleset.constants.Warnings.COMMENTED_OUT_CODE import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT @@ -52,7 +52,7 @@ class CommentsRule(configRules: List) : DiktatRule( val errorNodesWithText: ListOfPairs = mutableListOf() val eolCommentsOffsetToText = getOffsetsToTextBlocksFromEolComments(node, errorNodesWithText) val blockCommentsOffsetToText = node - .findAllNodesWithSpecificType(BLOCK_COMMENT) + .findAllDescendantsWithSpecificType(BLOCK_COMMENT) .map { errorNodesWithText.add(it to it.text.trim().removeSurrounding("/*", "*/")) it.startOffset to it.text.trim().removeSurrounding("/*", "*/") @@ -80,7 +80,7 @@ class CommentsRule(configRules: List) : DiktatRule( } .filter { (_, parsedNode) -> parsedNode - .findAllNodesWithSpecificType(TokenType.ERROR_ELEMENT) + .findAllDescendantsWithSpecificType(TokenType.ERROR_ELEMENT) .isEmpty() } .forEach { (offset, parsedNode) -> @@ -97,7 +97,7 @@ class CommentsRule(configRules: List) : DiktatRule( */ private fun getOffsetsToTextBlocksFromEolComments(node: ASTNode, errorNodesWithText: ListOfPairs): List> { val comments = node - .findAllNodesWithSpecificType(EOL_COMMENT) + .findAllDescendantsWithSpecificType(EOL_COMMENT) .filter { !it.text.contains(eolCommentStart) || isCodeAfterCommentStart(it.text) } return if (comments.isNotEmpty()) { val result = mutableListOf(mutableListOf(comments.first())) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/CommentsFormatting.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/CommentsFormatting.kt index d617286972..0f3192a298 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/CommentsFormatting.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/CommentsFormatting.kt @@ -270,10 +270,10 @@ class CommentsFormatting(configRules: List) : DiktatRule( EOL_COMMENT -> (node as LeafPsiElement).replaceWithText("// $commentText") BLOCK_COMMENT -> (node as LeafPsiElement).replaceWithText("/* $commentText") KDOC -> { - node.findAllNodesWithSpecificType(KDOC_TEXT).forEach { + node.findAllDescendantsWithSpecificType(KDOC_TEXT).forEach { modifyKdocText(it, configuration) } - node.findAllNodesWithSpecificType(KDOC_CODE_BLOCK_TEXT).forEach { + node.findAllDescendantsWithSpecificType(KDOC_CODE_BLOCK_TEXT).forEach { modifyKdocText(it, configuration) } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocMethods.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocMethods.kt index 7957f96b96..fda5c905e0 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocMethods.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocMethods.kt @@ -10,7 +10,7 @@ import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_ON_FUNCTION import org.cqfn.diktat.ruleset.rules.DiktatRule 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.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.findChildAfter import org.cqfn.diktat.ruleset.utils.getBodyLines import org.cqfn.diktat.ruleset.utils.getFilePath @@ -155,7 +155,7 @@ class KdocMethods(configRules: List) : DiktatRule( private fun getExplicitlyThrownExceptions(node: ASTNode): Set { val codeBlock = node.getFirstChildWithType(BLOCK) - val throwKeywords = codeBlock?.findAllNodesWithSpecificType(THROW) + val throwKeywords = codeBlock?.findAllDescendantsWithSpecificType(THROW) return throwKeywords ?.map { it.psi as KtThrowExpression } ?.mapNotNull { it.thrownExpression?.referenceExpression()?.text } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BlockStructureBraces.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BlockStructureBraces.kt index 49a15eab5c..3cd13a9842 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BlockStructureBraces.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BlockStructureBraces.kt @@ -91,7 +91,7 @@ class BlockStructureBraces(configRules: List) : DiktatRule( val catchBlocks = tryBlock.catchClauses.map { it.node } val finallyBlock = tryBlock.finallyBlock?.node checkOpenBraceOnSameLine(tryBlock.node, BLOCK, configuration) - val allMiddleSpaceNodes = node.findAllNodesWithSpecificType(CATCH).map { it.treePrev } + val allMiddleSpaceNodes = node.findAllDescendantsWithSpecificType(CATCH).map { it.treePrev } checkMidBrace(allMiddleSpaceNodes, node, CATCH_KEYWORD) catchBlocks.forEach { checkOpenBraceOnSameLine(it, BLOCK, configuration) @@ -100,7 +100,7 @@ class BlockStructureBraces(configRules: List) : DiktatRule( finallyBlock?.let { block -> checkOpenBraceOnSameLine(block, BLOCK, configuration) checkCloseBrace(block.findChildByType(BLOCK)!!, configuration) - val newAllMiddleSpaceNodes = node.findAllNodesWithSpecificType(FINALLY).map { it.treePrev } + val newAllMiddleSpaceNodes = node.findAllDescendantsWithSpecificType(FINALLY).map { it.treePrev } checkMidBrace(newAllMiddleSpaceNodes, node, FINALLY_KEYWORD) } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ClassLikeStructuresOrderRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ClassLikeStructuresOrderRule.kt index bcfddb8934..12a6d7ba4f 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ClassLikeStructuresOrderRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ClassLikeStructuresOrderRule.kt @@ -80,7 +80,7 @@ class ClassLikeStructuresOrderRule(configRules: List) : DiktatRule( node .parents() .last() - .findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + .findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) .any { ref -> ref.parent({ it == classNode }) == null && ref.text.contains(identifierNode.text) } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/NullableTypeRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/NullableTypeRule.kt index 6ef6d78631..7d0bb0e8fb 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/NullableTypeRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/NullableTypeRule.kt @@ -4,7 +4,7 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.NULLABLE_PROPERTY_TYPE import org.cqfn.diktat.ruleset.rules.DiktatRule import org.cqfn.diktat.ruleset.utils.KotlinParser -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.hasChildOfType import com.pinterest.ktlint.core.ast.ElementType.BOOLEAN_CONSTANT @@ -53,7 +53,7 @@ class NullableTypeRule(configRules: List) : DiktatRule( val typeReferenceNode = node.findChildByType(TYPE_REFERENCE)!! // check that property has nullable type, right value one of allow expression if (!node.hasChildOfType(NULL) && - node.findAllNodesWithSpecificType(DOT_QUALIFIED_EXPRESSION).isEmpty() && + node.findAllDescendantsWithSpecificType(DOT_QUALIFIED_EXPRESSION).isEmpty() && typeReferenceNode.hasChildOfType(NULLABLE_TYPE) && typeReferenceNode.findChildByType(NULLABLE_TYPE)!!.hasChildOfType(QUEST) && (node.findChildByType(CALL_EXPRESSION)?.findChildByType(REFERENCE_EXPRESSION) == null || diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/SortRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/SortRule.kt index 51fee533d6..fb1d2a3a90 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/SortRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/SortRule.kt @@ -5,7 +5,7 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.getRuleConfig import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_DECLARATIONS_ORDER import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.hasChildOfType import org.cqfn.diktat.ruleset.utils.isClassEnum @@ -81,7 +81,7 @@ class SortRule(configRules: List) : DiktatRule( nonSortList: List, node: ASTNode) { val isEnum = nonSortList.first().elementType == ENUM_ENTRY - val spaceBefore = if (node.findAllNodesWithSpecificType(EOL_COMMENT).isNotEmpty() && isEnum) { + val spaceBefore = if (node.findAllDescendantsWithSpecificType(EOL_COMMENT).isNotEmpty() && isEnum) { nonSortList.last().run { if (this.hasChildOfType(EOL_COMMENT) && !this.hasChildOfType(COMMA)) { this.addChild(LeafPsiElement(COMMA, ","), this.findChildByType(EOL_COMMENT)) @@ -142,7 +142,7 @@ class SortRule(configRules: List) : DiktatRule( val hasTrailingComma = (sortList.last() != enumEntryList.last() && enumEntryList.last().hasChildOfType(COMMA)) swapSortNodes(sortList, enumEntryList, node) if (!hasTrailingComma) { - val lastEntry = node.findAllNodesWithSpecificType(ENUM_ENTRY).last() + val lastEntry = node.findAllDescendantsWithSpecificType(ENUM_ENTRY).last() lastEntry.removeChild(lastEntry.findChildByType(COMMA)!!) } if (isEndSpace) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringConcatenationRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringConcatenationRule.kt index 9b2be11948..0c3fee3624 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringConcatenationRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringConcatenationRule.kt @@ -3,26 +3,39 @@ package org.cqfn.diktat.ruleset.rules.chapter3 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.STRING_CONCATENATION import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType -import org.cqfn.diktat.ruleset.utils.findParentNodeWithSpecificType -import org.cqfn.diktat.ruleset.utils.getFirstChildWithType +import org.cqfn.diktat.ruleset.utils.* import com.pinterest.ktlint.core.ast.ElementType.BINARY_EXPRESSION +import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION +import com.pinterest.ktlint.core.ast.ElementType.DOT_QUALIFIED_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.OPERATION_REFERENCE import com.pinterest.ktlint.core.ast.ElementType.PLUS import com.pinterest.ktlint.core.ast.ElementType.STRING_TEMPLATE import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtConstantExpression +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtOperationExpression +import org.jetbrains.kotlin.psi.KtParenthesizedExpression +import org.jetbrains.kotlin.psi.KtReferenceExpression +import org.jetbrains.kotlin.psi.KtStringTemplateExpression /** * This rule covers checks and fixes related to string concatenation. * Rule 3.8 prohibits string concatenation and suggests to use string templates instead - * // FixMe: fixes will be added - * // FixMe: .toString() method and functions that return strings are not supported + * if this expressions fits one line. For example: + * """ string """ + "string" will be converted to "string string" + * "string " + 1 will be converted to "string 1" + * "string one " + "string two " */ class StringConcatenationRule(configRules: List) : DiktatRule( "string-concatenation", configRules, - listOf(STRING_CONCATENATION)) { + listOf( + STRING_CONCATENATION + ) +) { override fun logic(node: ASTNode) { if (node.elementType == BINARY_EXPRESSION) { // searching top-level binary expression to detect any operations with "plus" (+) @@ -36,25 +49,56 @@ class StringConcatenationRule(configRules: List) : DiktatRule( private fun isSingleLineStatement(node: ASTNode): Boolean = !node.text.contains("\n") - private fun detectStringConcatenation(node: ASTNode) { - node.findAllNodesWithSpecificType(BINARY_EXPRESSION).find { detectStringConcatenationInExpression(it, node) } + /** + * This method works only with top-level binary expressions. It should be checked before the call. + */ + @Suppress("AVOID_NULL_CHECKS") + private fun detectStringConcatenation(topLevelBinaryExpr: ASTNode) { + val allBinaryExpressions = topLevelBinaryExpr.findAllDescendantsWithSpecificType(BINARY_EXPRESSION) + val nodeWithBug = allBinaryExpressions.find { isDetectStringConcatenationInExpression(it) } + + if (nodeWithBug != null) { + STRING_CONCATENATION.warnAndFix( + configRules, emitWarn, + this.isFixMode, topLevelBinaryExpr.text.lines().first(), nodeWithBug.startOffset, nodeWithBug + ) { + fixBinaryExpressionWithConcatenation(nodeWithBug) + loop(topLevelBinaryExpr.treeParent) + } + } } - @Suppress("FUNCTION_BOOLEAN_PREFIX") - private fun detectStringConcatenationInExpression(node: ASTNode, parentNode: ASTNode): Boolean { - assert(node.elementType == BINARY_EXPRESSION) - val firstChild = node.firstChildNode - return if (isPlusBinaryExpression(node) && firstChild.elementType == STRING_TEMPLATE) { - STRING_CONCATENATION.warn(configRules, emitWarn, this.isFixMode, parentNode.text, firstChild.startOffset, firstChild) - true - } else { - false + private fun loop(parentTopLevelBinaryExpr: ASTNode) { + val allBinaryExpressions = parentTopLevelBinaryExpr.findAllDescendantsWithSpecificType(BINARY_EXPRESSION) + val nodeWithBug = allBinaryExpressions.find { isDetectStringConcatenationInExpression(it) } + + val bugDetected = nodeWithBug != null + if (bugDetected) { + fixBinaryExpressionWithConcatenation(nodeWithBug) + loop(parentTopLevelBinaryExpr) } } + /** + * We can detect string concatenation by the first (left) operand in binary expression. + * If it is of type string - then we found string concatenation. + * If the right value is not a constant string then don't change them to template. + */ + private fun isDetectStringConcatenationInExpression(node: ASTNode): Boolean { + require(node.elementType == BINARY_EXPRESSION) { + "cannot process non binary expression in the process of detecting string concatenation" + } + val firstChild = node.firstChildNode + val lastChild = node.lastChildNode + return isPlusBinaryExpression(node) && isStringVar(firstChild, lastChild) + } + + private fun isStringVar(firstChild: ASTNode, lastChild: ASTNode) = firstChild.elementType == STRING_TEMPLATE || + ((firstChild.text.endsWith("toString()")) && firstChild.elementType == DOT_QUALIFIED_EXPRESSION && lastChild.elementType == STRING_TEMPLATE) + @Suppress("COMMENT_WHITE_SPACE") private fun isPlusBinaryExpression(node: ASTNode): Boolean { - assert(node.elementType == BINARY_EXPRESSION) + require(node.elementType == BINARY_EXPRESSION) // binary expression // / | \ // expr1 operationRef expr2 @@ -63,4 +107,112 @@ class StringConcatenationRule(configRules: List) : DiktatRule( return operationReference ?.getFirstChildWithType(PLUS) != null } + + private fun fixBinaryExpressionWithConcatenation(node: ASTNode?) { + val binaryExpressionPsi = node?.psi as KtBinaryExpression + val parentNode = node.treeParent + val textNode = checkKtExpression(binaryExpressionPsi) + val newNode = KotlinParser().createNode("\"$textNode\"") + parentNode.replaceChild(node, newNode) + } + + private fun isPlusBinaryExpressionAndFirstElementString(binaryExpressionNode: KtBinaryExpression) = + (binaryExpressionNode.left is KtStringTemplateExpression) && PLUS == binaryExpressionNode.operationToken + + @Suppress( + "TOO_LONG_FUNCTION", + "NESTED_BLOCK", + "SAY_NO_TO_VAR", + "ComplexMethod") + private fun checkKtExpression(binaryExpressionPsi: KtBinaryExpression): String { + var lvalueText = binaryExpressionPsi.left?.text?.trim('"') + val rvalueText = binaryExpressionPsi.right?.text + + if (binaryExpressionPsi.isLvalueDotQualifiedExpression() && binaryExpressionPsi.firstChild.text.endsWith("toString()")) { + // =========== (1 + 2).toString() -> ${(1 + 2)} + val leftText = binaryExpressionPsi.firstChild.firstChild.text + lvalueText = "\${$leftText}" + } + if (binaryExpressionPsi.isLvalueReferenceExpression() || binaryExpressionPsi.isLvalueConstantExpression()) { + return binaryExpressionPsi.text + } + if (binaryExpressionPsi.isLvalueBinaryExpression()) { + val rightValue = checkKtExpression(binaryExpressionPsi.left as KtBinaryExpression) + val rightEx = binaryExpressionPsi.right + val rightVal = if (binaryExpressionPsi.isRvalueParenthesized()) { + checkKtExpression(rightEx?.children?.get(0) as KtBinaryExpression) + } else { + (rightEx?.text?.trim('"')) + } + if (binaryExpressionPsi.left?.text == rightValue) { + return binaryExpressionPsi.text + } + return "$rightValue$rightVal" + } else if (binaryExpressionPsi.isRvalueConstantExpression() || binaryExpressionPsi.isRvalueStringTemplateExpression()) { + // =========== "a " + "b" -> "a b" + val rvalueTextNew = rvalueText?.trim('"') + return "$lvalueText$rvalueTextNew" + } else if (binaryExpressionPsi.isRvalueCallExpression() || binaryExpressionPsi.isRvalueDotQualifiedExpression() || binaryExpressionPsi.isRvalueOperation()) { + // =========== "a " + foo() -> "a ${foo()}}" + return "$lvalueText\${$rvalueText}" + } else if (binaryExpressionPsi.isRvalueReferenceExpression()) { + // =========== "a " + b -> "a $b" + return "$lvalueText$$rvalueText" + } else if (binaryExpressionPsi.isRvalueParenthesized()) { + val binExpression = binaryExpressionPsi.right?.children?.first() + if (binExpression is KtBinaryExpression) { + if (isPlusBinaryExpressionAndFirstElementString(binExpression)) { + val rightValue = checkKtExpression(binExpression) + return "$lvalueText$rightValue" + } else if (binExpression.isLvalueBinaryExpression()) { + val rightValue = checkKtExpression(binExpression.left as KtBinaryExpression) + val rightEx = binExpression.right + val rightVal = if (binExpression.isRvalueParenthesized()) { + checkKtExpression(rightEx?.children?.get(0) as KtBinaryExpression) + } else { + (rightEx?.text?.trim('"')) + } + if (binExpression.left?.text == rightValue) { + return "$lvalueText\${$rvalueText}" + } + return "$lvalueText$rightValue$rightVal" + } + } + return "$lvalueText\${$rvalueText}" + } + return binaryExpressionPsi.text + } + + private fun KtBinaryExpression.isRvalueConstantExpression() = + this.right is KtConstantExpression + + private fun KtBinaryExpression.isRvalueStringTemplateExpression() = + this.right is KtStringTemplateExpression + + private fun KtBinaryExpression.isRvalueCallExpression() = + this.right is KtCallExpression + + private fun KtBinaryExpression.isRvalueReferenceExpression() = + this.right is KtReferenceExpression + + private fun KtBinaryExpression.isRvalueDotQualifiedExpression() = + this.right is KtDotQualifiedExpression + + private fun KtBinaryExpression.isRvalueParenthesized() = + this.right is KtParenthesizedExpression + + private fun KtBinaryExpression.isRvalueOperation() = + this.right is KtOperationExpression + + private fun KtBinaryExpression.isLvalueDotQualifiedExpression() = + this.left is KtDotQualifiedExpression + + private fun KtBinaryExpression.isLvalueBinaryExpression() = + this.left is KtBinaryExpression + + private fun KtBinaryExpression.isLvalueReferenceExpression() = + this.left is KtReferenceExpression + + private fun KtBinaryExpression.isLvalueConstantExpression() = + this.left is KtConstantExpression } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringTemplateFormatRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringTemplateFormatRule.kt index 40d5b607cf..7d20ed9548 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringTemplateFormatRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringTemplateFormatRule.kt @@ -1,11 +1,10 @@ package org.cqfn.diktat.ruleset.rules.chapter3 import org.cqfn.diktat.common.config.rules.RulesConfig -import org.cqfn.diktat.ruleset.constants.EmitType import org.cqfn.diktat.ruleset.constants.Warnings.STRING_TEMPLATE_CURLY_BRACES import org.cqfn.diktat.ruleset.constants.Warnings.STRING_TEMPLATE_QUOTES import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.hasAnyChildOfTypes import com.pinterest.ktlint.core.ast.ElementType.ARRAY_ACCESS_EXPRESSION @@ -84,13 +83,13 @@ class StringTemplateFormatRule(configRules: List) : DiktatRule( @Suppress("UnsafeCallOnNullableType", "FUNCTION_BOOLEAN_PREFIX") private fun bracesCanBeOmitted(node: ASTNode): Boolean { val onlyOneRefExpr = node - .findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + .findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) .singleOrNull() ?.treeParent ?.elementType == LONG_STRING_TEMPLATE_ENTRY val isArrayAccessExpression = node // this should be omitted in previous expression, used for safe warranties - .findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + .findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) .singleOrNull() ?.treeParent ?.elementType == ARRAY_ACCESS_EXPRESSION diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt index a6b2eb85b6..846e192fcf 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt @@ -14,7 +14,7 @@ import org.cqfn.diktat.ruleset.rules.DiktatRule import org.cqfn.diktat.ruleset.rules.chapter1.PackageNaming.Companion.PACKAGE_SEPARATOR import org.cqfn.diktat.ruleset.utils.StandardPlatforms import org.cqfn.diktat.ruleset.utils.copyrightWords -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.handleIncorrectOrder import org.cqfn.diktat.ruleset.utils.moveChildBefore import org.cqfn.diktat.ruleset.utils.operatorMap @@ -247,7 +247,7 @@ class FileStructureRule(configRules: List) : DiktatRule( } private fun findAllReferences(node: ASTNode) { - node.findAllNodesWithSpecificType(OPERATION_REFERENCE).forEach { ref -> + node.findAllDescendantsWithSpecificType(OPERATION_REFERENCE).forEach { ref -> if (!ref.isPartOf(IMPORT_DIRECTIVE)) { val references = operatorMap.filterValues { it == ref.text } if (references.isNotEmpty()) { @@ -258,7 +258,7 @@ class FileStructureRule(configRules: List) : DiktatRule( } } } - node.findAllNodesWithSpecificType(REFERENCE_EXPRESSION).forEach { + node.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION).forEach { if (!it.isPartOf(IMPORT_DIRECTIVE)) { // the importedName method removes the quotes, but the node.text method does not refSet.add(it.text.replace("`", "")) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/NewlinesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/NewlinesRule.kt index 01829f787e..5f4fe3c62e 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/NewlinesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/NewlinesRule.kt @@ -11,7 +11,7 @@ import org.cqfn.diktat.ruleset.rules.DiktatRule import org.cqfn.diktat.ruleset.utils.appendNewlineMergingWhiteSpace import org.cqfn.diktat.ruleset.utils.emptyBlockList import org.cqfn.diktat.ruleset.utils.extractLineOfText -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.getIdentifierName import org.cqfn.diktat.ruleset.utils.hasParent import org.cqfn.diktat.ruleset.utils.isBeginByNewline @@ -558,7 +558,7 @@ class NewlinesRule(configRules: List) : DiktatRule( parents().takeWhile { it.elementType in chainExpressionTypes && it.elementType != LAMBDA_ARGUMENT } private fun isMultilineLambda(node: ASTNode): Boolean = - node.findAllNodesWithSpecificType(LAMBDA_ARGUMENT) + node.findAllDescendantsWithSpecificType(LAMBDA_ARGUMENT) .firstOrNull() ?.text ?.count { it == '\n' } ?: -1 > 0 @@ -572,7 +572,7 @@ class NewlinesRule(configRules: List) : DiktatRule( val firstCallee = mutableListOf().also { getOrderedCallExpressions(psi, it) }.first() - findAllNodesWithSpecificType(firstCallee.elementType, false).first() === this@isFirstCall + findAllDescendantsWithSpecificType(firstCallee.elementType, false).first() === this@isFirstCall } ?: false /** diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/NullChecksRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/NullChecksRule.kt index 51ce63c06d..6627d48e15 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/NullChecksRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/NullChecksRule.kt @@ -61,7 +61,7 @@ class NullChecksRule(configRules: List) : DiktatRule( } private fun conditionInIfStatement(node: ASTNode) { - node.findAllNodesWithSpecificType(BINARY_EXPRESSION).forEach { binaryExprNode -> + node.findAllDescendantsWithSpecificType(BINARY_EXPRESSION).forEach { binaryExprNode -> val condition = (binaryExprNode.psi as KtBinaryExpression) if (isNullCheckBinaryExpression(condition)) { when (condition.operationToken) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/SmartCastRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/SmartCastRule.kt index ba59348f89..f0a09c9aae 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/SmartCastRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/SmartCastRule.kt @@ -4,7 +4,7 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.SMART_CAST_NEEDED import org.cqfn.diktat.ruleset.rules.DiktatRule import org.cqfn.diktat.ruleset.utils.KotlinParser -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.findParentNodeWithSpecificType import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType @@ -172,7 +172,7 @@ class SmartCastRule(configRules: List) : DiktatRule( thenBlock?.let { // Find all as expressions that are inside this current block val asList = thenBlock - .findAllNodesWithSpecificType(BINARY_WITH_TYPE) + .findAllDescendantsWithSpecificType(BINARY_WITH_TYPE) .filter { it.text.contains(" as ") && it.findParentNodeWithSpecificType(BLOCK) == thenBlock } @@ -180,7 +180,7 @@ class SmartCastRule(configRules: List) : DiktatRule( checkAsExpressions(asList, blocks) } ?: run { - val asList = then.findAllNodesWithSpecificType(BINARY_WITH_TYPE).filter { it.text.contains(KtTokens.AS_KEYWORD.value) } + val asList = then.findAllDescendantsWithSpecificType(BINARY_WITH_TYPE).filter { it.text.contains(KtTokens.AS_KEYWORD.value) } checkAsExpressions(asList, blocks) } } @@ -229,7 +229,7 @@ class SmartCastRule(configRules: List) : DiktatRule( val type = entry.getFirstChildWithType(WHEN_CONDITION_IS_PATTERN)!! .getFirstChildWithType(TYPE_REFERENCE)?.text - val callExpr = entry.findAllNodesWithSpecificType(BINARY_WITH_TYPE).firstOrNull() + val callExpr = entry.findAllDescendantsWithSpecificType(BINARY_WITH_TYPE).firstOrNull() val blocks = listOf(IsExpressions(identifier, type ?: "")) callExpr?.let { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/TypeAliasRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/TypeAliasRule.kt index 1804b09aa1..cd8d5dca83 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/TypeAliasRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/TypeAliasRule.kt @@ -37,7 +37,7 @@ class TypeAliasRule(configRules: List) : DiktatRule( */ private fun checkTypeReference(node: ASTNode, config: TypeAliasConfiguration) { if (node.textLength > config.typeReferenceLength) { - if (node.findAllNodesWithSpecificType(LT).size > 1 || node.findAllNodesWithSpecificType(VALUE_PARAMETER).size > 1) { + if (node.findAllDescendantsWithSpecificType(LT).size > 1 || node.findAllDescendantsWithSpecificType(VALUE_PARAMETER).size > 1) { TYPE_ALIAS.warn(configRules, emitWarn, isFixMode, "too long type reference", node.startOffset, node) } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/AvoidNestedFunctionsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/AvoidNestedFunctionsRule.kt index 17eff7b2e9..56e047d780 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/AvoidNestedFunctionsRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/AvoidNestedFunctionsRule.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter5 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.AVOID_NESTED_FUNCTIONS import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType import org.cqfn.diktat.ruleset.utils.hasChildOfType import org.cqfn.diktat.ruleset.utils.hasParent @@ -43,7 +43,7 @@ class AvoidNestedFunctionsRule(configRules: List) : DiktatRule( AVOID_NESTED_FUNCTIONS.warnAndFix(configRules, emitWarn, isFixMode, "fun $funcName", node.startOffset, node, canBeAutoCorrected = checkFunctionReferences(node)) { // We take last nested function, then add and remove child from bottom to top - val lastFunc = node.findAllNodesWithSpecificType(FUN).last() + val lastFunc = node.findAllDescendantsWithSpecificType(FUN).last() val funcSeq = lastFunc .parents() .filter { it.elementType == FUN } @@ -78,14 +78,14 @@ class AvoidNestedFunctionsRule(configRules: List) : DiktatRule( @Suppress("UnsafeCallOnNullableType", "FUNCTION_BOOLEAN_PREFIX") private fun checkFunctionReferences(func: ASTNode): Boolean { val localProperties: MutableList = mutableListOf() - localProperties.addAll(func.findAllNodesWithSpecificType(PROPERTY)) + localProperties.addAll(func.findAllDescendantsWithSpecificType(PROPERTY)) val propertiesNames: List = mutableListOf().apply { addAll(localProperties.map { it.getFirstChildWithType(IDENTIFIER)!!.text }) addAll(getParameterNames(func)) } .toList() - return func.findAllNodesWithSpecificType(REFERENCE_EXPRESSION).all { propertiesNames.contains(it.text) } + return func.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION).all { propertiesNames.contains(it.text) } } /** diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/LambdaLengthRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/LambdaLengthRule.kt index 77fe03e357..de961cdbde 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/LambdaLengthRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/LambdaLengthRule.kt @@ -38,7 +38,7 @@ class LambdaLengthRule(configRules: List) : DiktatRule( node.treeParent.removeChild(node) } } - val isIt = copyNode.findAllNodesWithSpecificType(ElementType.REFERENCE_EXPRESSION).map { re -> re.text }.contains("it") + val isIt = copyNode.findAllDescendantsWithSpecificType(ElementType.REFERENCE_EXPRESSION).map { re -> re.text }.contains("it") val parameters = node.findChildByType(ElementType.FUNCTION_LITERAL)?.findChildByType(ElementType.VALUE_PARAMETER_LIST) if (parameters == null && isIt) { TOO_MANY_LINES_IN_LAMBDA.warn(configRules, emitWarn, isFixMode, diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/NestedFunctionBlock.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/NestedFunctionBlock.kt index 66cbf9bd5a..bb93f77f3e 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/NestedFunctionBlock.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/NestedFunctionBlock.kt @@ -5,7 +5,7 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.getRuleConfig import org.cqfn.diktat.ruleset.constants.Warnings.NESTED_BLOCK import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.hasChildOfType import com.pinterest.ktlint.core.ast.ElementType.CLASS @@ -35,7 +35,7 @@ class NestedFunctionBlock(configRules: List) : DiktatRule( } private fun countNestedBlocks(node: ASTNode, maxNestedBlockCount: Long) { - node.findAllNodesWithSpecificType(LBRACE) + node.findAllDescendantsWithSpecificType(LBRACE) .reversed() .forEach { lbraceNode -> val blockParent = lbraceNode diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt index 09cae45047..ebf37b90d5 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter6 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.EXTENSION_FUNCTION_WITH_CLASS import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType @@ -36,7 +36,7 @@ class ExtensionFunctionsInFileRule(configRules: List) : DiktatRule( @Suppress("UnsafeCallOnNullableType") private fun collectAllClassNames(node: ASTNode): List { - val classes = node.findAllNodesWithSpecificType(CLASS) + val classes = node.findAllDescendantsWithSpecificType(CLASS) return classes.map { (it.psi as KtClass).name!! } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsSameNameRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsSameNameRule.kt index 2f37ae2de6..0ff833175a 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsSameNameRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsSameNameRule.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter6 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.findChildAfter import org.cqfn.diktat.ruleset.utils.findChildBefore import org.cqfn.diktat.ruleset.utils.findLeafWithSpecificType @@ -53,7 +53,7 @@ class ExtensionFunctionsSameNameRule(configRules: List) : DiktatRul @Suppress("UnsafeCallOnNullableType", "TYPE_ALIAS") private fun collectAllRelatedClasses(node: ASTNode): List> { val classListWithInheritance = node - .findAllNodesWithSpecificType(CLASS) + .findAllDescendantsWithSpecificType(CLASS) .filterNot { (it.psi as KtClass).isInterface() } .filter { it.hasChildOfType(SUPER_TYPE_LIST) } @@ -72,7 +72,7 @@ class ExtensionFunctionsSameNameRule(configRules: List) : DiktatRul @Suppress("UnsafeCallOnNullableType", "TYPE_ALIAS") private fun collectAllExtensionFunctions(node: ASTNode): SimilarSignatures { - val extensionFunctionList = node.findAllNodesWithSpecificType(FUN).filter { it.hasChildOfType(TYPE_REFERENCE) && it.hasChildOfType(DOT) } + val extensionFunctionList = node.findAllDescendantsWithSpecificType(FUN).filter { it.hasChildOfType(TYPE_REFERENCE) && it.hasChildOfType(DOT) } val distinctFunctionSignatures: MutableMap = mutableMapOf() // maps function signatures on node it is used by val extensionFunctionsPairs: MutableList> = mutableListOf() // pairs extension functions with same signature diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ImplicitBackingPropertyRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ImplicitBackingPropertyRule.kt index 23116a6607..53a4398bd2 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ImplicitBackingPropertyRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ImplicitBackingPropertyRule.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter6 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.NO_CORRESPONDING_PROPERTY import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType import org.cqfn.diktat.ruleset.utils.hasAnyChildOfTypes import org.cqfn.diktat.ruleset.utils.hasChildOfType @@ -50,7 +50,7 @@ class ImplicitBackingPropertyRule(configRules: List) : DiktatRule( } private fun validateAccessors(node: ASTNode, propsWithBackSymbol: List) { - val accessors = node.findAllNodesWithSpecificType(PROPERTY_ACCESSOR).filter { it.hasChildOfType(BLOCK) } // exclude get with expression body + val accessors = node.findAllDescendantsWithSpecificType(PROPERTY_ACCESSOR).filter { it.hasChildOfType(BLOCK) } // exclude get with expression body accessors.filter { it.hasChildOfType(GET_KEYWORD) }.forEach { handleGetAccessors(it, node, propsWithBackSymbol) } accessors.filter { it.hasChildOfType(SET_KEYWORD) }.forEach { handleSetAccessors(it, node, propsWithBackSymbol) } @@ -62,12 +62,12 @@ class ImplicitBackingPropertyRule(configRules: List) : DiktatRule( node: ASTNode, propsWithBackSymbol: List) { val refExprs = accessor - .findAllNodesWithSpecificType(RETURN) + .findAllDescendantsWithSpecificType(RETURN) .filterNot { it.hasChildOfType(DOT_QUALIFIED_EXPRESSION) } - .flatMap { it.findAllNodesWithSpecificType(REFERENCE_EXPRESSION) } + .flatMap { it.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) } val localProps = accessor - .findAllNodesWithSpecificType(PROPERTY) + .findAllDescendantsWithSpecificType(PROPERTY) .map { (it.psi as KtProperty).name!! } // If refExprs is empty then we assume that it returns some constant if (refExprs.isNotEmpty()) { @@ -80,7 +80,7 @@ class ImplicitBackingPropertyRule(configRules: List) : DiktatRule( accessor: ASTNode, node: ASTNode, propsWithBackSymbol: List) { - val refExprs = accessor.findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + val refExprs = accessor.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) // In set we don't check for local properties. At least one reference expression should contain field or _prop if (refExprs.isNotEmpty()) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/PropertyAccessorFields.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/PropertyAccessorFields.kt index 10d05e6136..7ac7671968 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/PropertyAccessorFields.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/PropertyAccessorFields.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter6 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.isGoingAfter import com.pinterest.ktlint.core.ast.ElementType.BLOCK @@ -34,7 +34,7 @@ class PropertyAccessorFields(configRules: List) : DiktatRule( private fun checkPropertyAccessor(node: ASTNode) { val leftValue = node.treeParent.findChildByType(IDENTIFIER) ?: return val firstReferenceWithSameName = node - .findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + .findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) .mapNotNull { it.findChildByType(IDENTIFIER) } .firstOrNull { it.text == leftValue.text && diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/TrivialPropertyAccessors.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/TrivialPropertyAccessors.kt index 899f71f390..144cf594b1 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/TrivialPropertyAccessors.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/TrivialPropertyAccessors.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter6 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType import org.cqfn.diktat.ruleset.utils.getIdentifierName import org.cqfn.diktat.ruleset.utils.hasChildOfType @@ -70,7 +70,7 @@ class TrivialPropertyAccessors(configRules: List) : DiktatRule( @Suppress("UnsafeCallOnNullableType") private fun handleGetAccessor(node: ASTNode) { // It handles both cases: get() = ... and get() { return ... } - val references = node.findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + val references = node.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) if (references.singleOrNull()?.text == "field") { raiseWarning(node) } else if (node.getChildren(null).size == ONE_CHILD_IN_ARRAY) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/UselessSupertype.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/UselessSupertype.kt index 9b63c527fb..9e041c9309 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/UselessSupertype.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/UselessSupertype.kt @@ -47,7 +47,7 @@ class UselessSupertype(configRules: List) : DiktatRule( ?.findAllNodesWithCondition({ it.elementType in superType }) ?.takeIf { it.isNotEmpty() } ?: return val qualifiedSuperCalls = node - .findAllNodesWithSpecificType(DOT_QUALIFIED_EXPRESSION) + .findAllDescendantsWithSpecificType(DOT_QUALIFIED_EXPRESSION) .mapNotNull { findFunWithSuper(it) } .ifEmpty { return } if (superNodes.size == 1) { @@ -93,10 +93,10 @@ class UselessSupertype(configRules: List) : DiktatRule( private fun findFunWithSuper(node: ASTNode) = Pair( node.findChildByType(SUPER_EXPRESSION) ?.findChildByType(TYPE_REFERENCE) - ?.findAllNodesWithSpecificType(IDENTIFIER) + ?.findAllDescendantsWithSpecificType(IDENTIFIER) ?.firstOrNull(), node.findChildByType(CALL_EXPRESSION) - ?.findAllNodesWithSpecificType(IDENTIFIER) + ?.findAllDescendantsWithSpecificType(IDENTIFIER) ?.firstOrNull()) .run { if (first == null || second == null) null else first!! to second!! @@ -114,7 +114,7 @@ class UselessSupertype(configRules: List) : DiktatRule( private fun findAllSupers(superTypeList: List, methodsName: List): Map? { val fileNode = superTypeList.first().parent({ it.elementType == FILE })!! val superNodesIdentifier = superTypeList.map { - it.findAllNodesWithSpecificType(IDENTIFIER) + it.findAllDescendantsWithSpecificType(IDENTIFIER) .first() .text } @@ -127,7 +127,7 @@ class UselessSupertype(configRules: List) : DiktatRule( } val functionNameMap: HashMap = hashMapOf() superNodes.forEach { classBody -> - val overrideFunctions = classBody.findAllNodesWithSpecificType(FUN) + val overrideFunctions = classBody.findAllDescendantsWithSpecificType(FUN) .filter { (if (classBody.treeParent.hasChildOfType(CLASS_KEYWORD)) it.findChildByType(MODIFIER_LIST)!!.hasChildOfType(OPEN_KEYWORD) else true) && it.getIdentifierName()!!.text in methodsName diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/DataClassesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/DataClassesRule.kt index 0080391d0e..2f03d76afa 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/DataClassesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/DataClassesRule.kt @@ -82,7 +82,7 @@ class DataClassesRule(configRules: List) : DiktatRule( if (constructorParametersNames.isNotEmpty()) { val initBlocks = findChildByType(CLASS_BODY)?.getAllChildrenWithType(CLASS_INITIALIZER) initBlocks?.forEach { init -> - val refExpressions = init.findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + val refExpressions = init.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) if (refExpressions.any { it.text in constructorParametersNames }) { return false } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/StatelessClassesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/StatelessClassesRule.kt index 3512aaa33e..a55863b351 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/StatelessClassesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/StatelessClassesRule.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter6.classes import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.OBJECT_IS_PREFERRED import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType import org.cqfn.diktat.ruleset.utils.hasChildOfType @@ -35,10 +35,10 @@ class StatelessClassesRule(configRules: List) : DiktatRule( // Fixme: We should find interfaces in all project and then check them if (node.elementType == FILE) { val interfacesNodes = node - .findAllNodesWithSpecificType(CLASS) + .findAllDescendantsWithSpecificType(CLASS) .filter { it.hasChildOfType(INTERFACE_KEYWORD) } node - .findAllNodesWithSpecificType(CLASS) + .findAllDescendantsWithSpecificType(CLASS) .filterNot { it.hasChildOfType(INTERFACE_KEYWORD) } .forEach { handleClass(it, interfacesNodes) } } @@ -64,7 +64,7 @@ class StatelessClassesRule(configRules: List) : DiktatRule( private fun isStatelessClass(node: ASTNode): Boolean { val properties = (node.psi as KtClass).getProperties() - val functions = node.findAllNodesWithSpecificType(FUN) + val functions = node.findAllDescendantsWithSpecificType(FUN) return properties.isNullOrEmpty() && functions.isNotEmpty() && !(node.psi as KtClass).hasExplicitPrimaryConstructor() diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt index b872f1bb30..9905f4a9a3 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt @@ -92,7 +92,7 @@ fun ASTNode.isTextLengthInRange(range: IntRange): Boolean = this.textLength in r * @return node with type [IDENTIFIER] or null if it is not present */ fun ASTNode.getIdentifierName(): ASTNode? = - this.getChildren(null).find { it.elementType == ElementType.IDENTIFIER } + this.getFirstChildWithType(ElementType.IDENTIFIER) /** * getting first child name with TYPE_PARAMETER_LIST type @@ -100,20 +100,12 @@ fun ASTNode.getIdentifierName(): ASTNode? = * @return a node with type TYPE_PARAMETER_LIST or null if it is not present */ fun ASTNode.getTypeParameterList(): ASTNode? = - this.getChildren(null).find { it.elementType == ElementType.TYPE_PARAMETER_LIST } - -/** - * getting all children that have IDENTIFIER type - * - * @return a list of nodes - */ -fun ASTNode.getAllIdentifierChildren(): List = - this.getChildren(null).filter { it.elementType == ElementType.IDENTIFIER } + this.getFirstChildWithType(ElementType.TYPE_PARAMETER_LIST) /** * @return true if this node contains no error elements, false otherwise */ -fun ASTNode.isCorrect() = this.findAllNodesWithSpecificType(TokenType.ERROR_ELEMENT).isEmpty() +fun ASTNode.isCorrect() = this.findAllDescendantsWithSpecificType(TokenType.ERROR_ELEMENT).isEmpty() /** * obviously returns list with children that match particular element type @@ -143,7 +135,7 @@ fun ASTNode.replaceWhiteSpaceText(beforeNode: ASTNode, text: String) { * @return a node or null if it was not found */ fun ASTNode.getFirstChildWithType(elementType: IElementType): ASTNode? = - this.getChildren(null).find { it.elementType == elementType } + this.findChildByType(elementType) /** * Checks if the symbols in this node are at the end of line @@ -415,7 +407,7 @@ fun ASTNode.numNewLines() = text.count { it == '\n' } /** * This method performs tree traversal and returns all nodes with specific element type */ -fun ASTNode.findAllNodesWithSpecificType(elementType: IElementType, withSelf: Boolean = true) = +fun ASTNode.findAllDescendantsWithSpecificType(elementType: IElementType, withSelf: Boolean = true) = findAllNodesWithCondition({ it.elementType == elementType }, withSelf) /** @@ -503,7 +495,7 @@ fun ASTNode.hasSuppress(warningName: String) = parent({ node -> } else { node.findChildByType(FILE_ANNOTATION_LIST) } - annotationNode?.findAllNodesWithSpecificType(ANNOTATION_ENTRY) + annotationNode?.findAllDescendantsWithSpecificType(ANNOTATION_ENTRY) ?.map { it.psi as KtAnnotationEntry } ?.any { it.shortName.toString() == Suppress::class.simpleName && @@ -719,7 +711,7 @@ fun ASTNode.extractLineOfText(): String { */ fun ASTNode.hasTestAnnotation() = findChildByType(MODIFIER_LIST) ?.getAllChildrenWithType(ANNOTATION_ENTRY) - ?.flatMap { it.findAllNodesWithSpecificType(ElementType.CONSTRUCTOR_CALLEE) } + ?.flatMap { it.findAllDescendantsWithSpecificType(ElementType.CONSTRUCTOR_CALLEE) } ?.any { it.findLeafWithSpecificType(ElementType.IDENTIFIER)?.text == "Test" } ?: false diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesSearch.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesSearch.kt index be144ce1ca..7a04e90fa5 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesSearch.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesSearch.kt @@ -7,7 +7,7 @@ package org.cqfn.diktat.ruleset.utils.search -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.getDeclarationScope import org.cqfn.diktat.ruleset.utils.isGoingAfter @@ -50,7 +50,7 @@ abstract class VariablesSearch(val node: ASTNode, "To collect all variables in a file you need to provide file root node" } return node - .findAllNodesWithSpecificType(ElementType.PROPERTY) + .findAllDescendantsWithSpecificType(ElementType.PROPERTY) .map { it.psi as KtProperty } .filter(filterForVariables) .associateWith { it.getSearchResults() } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithAssignmentSearch.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithAssignmentSearch.kt index 4cdf7e7f3f..f0889812c5 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithAssignmentSearch.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithAssignmentSearch.kt @@ -10,7 +10,7 @@ package org.cqfn.diktat.ruleset.utils.search -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.isGoingAfter import com.pinterest.ktlint.core.ast.ElementType @@ -29,7 +29,7 @@ class VariablesWithAssignmentSearch(fileNode: ASTNode, * @return */ override fun KtElement.getAllSearchResults(property: KtProperty) = this.node - .findAllNodesWithSpecificType(ElementType.BINARY_EXPRESSION) + .findAllDescendantsWithSpecificType(ElementType.BINARY_EXPRESSION) // filtering out all usages that are declared in the same context but are going before the variable declaration // AND checking that there is an assignment .filter { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithUsagesSearch.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithUsagesSearch.kt index ccea032ad0..60b747d376 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithUsagesSearch.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithUsagesSearch.kt @@ -10,7 +10,7 @@ package org.cqfn.diktat.ruleset.utils.search -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.isGoingAfter import com.pinterest.ktlint.core.ast.ElementType @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.psi.KtProperty class VariablesWithUsagesSearch(fileNode: ASTNode, filterForVariables: (KtProperty) -> Boolean) : VariablesSearch(fileNode, filterForVariables) { override fun KtElement.getAllSearchResults(property: KtProperty) = this.node - .findAllNodesWithSpecificType(ElementType.REFERENCE_EXPRESSION) + .findAllDescendantsWithSpecificType(ElementType.REFERENCE_EXPRESSION) // filtering out all usages that are declared in the same context but are going before the variable declaration .filter { it.isGoingAfter(property.node) } .map { it.psi as KtNameReferenceExpression } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationRuleFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationRuleFixTest.kt new file mode 100644 index 0000000000..2705a85f33 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationRuleFixTest.kt @@ -0,0 +1,24 @@ +package org.cqfn.diktat.ruleset.chapter3 + +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.rules.chapter3.StringConcatenationRule +import org.cqfn.diktat.util.FixTestBase + +import generated.WarningNames +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class StringConcatenationRuleFixTest : FixTestBase( + "test/paragraph3/string_concatenation", + ::StringConcatenationRule, + listOf( + RulesConfig(Warnings.STRING_CONCATENATION.name, true, emptyMap()) + ) +) { + @Test + @Tag(WarningNames.STRING_CONCATENATION) + fun `fixing string concatenation`() { + fixAndCompare("StringConcatenationExpected.kt", "StringConcatenationTest.kt") + } +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationWarnTest.kt index b3c8d49a6d..90ff084190 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationWarnTest.kt @@ -12,7 +12,7 @@ import org.junit.jupiter.api.Test class StringConcatenationWarnTest : LintTestBase(::StringConcatenationRule) { private val ruleId = "$DIKTAT_RULE_SET_ID:string-concatenation" - private val canBeAutoCorrected = false + private val canBeAutoCorrected = true @Test @Tag(WarningNames.STRING_CONCATENATION) @@ -48,7 +48,9 @@ class StringConcatenationWarnTest : LintTestBase(::StringConcatenationRule) { """ | val a = (1 + 2).toString() + "my string" + 3 | - """.trimMargin() + """.trimMargin(), + LintError(1, 10, ruleId, Warnings.STRING_CONCATENATION.warnText() + + " (1 + 2).toString() + \"my string\" + 3", canBeAutoCorrected) ) } @@ -60,7 +62,9 @@ class StringConcatenationWarnTest : LintTestBase(::StringConcatenationRule) { | val myObject = 12 | val a = (1 + 2).toString() + "my string" + 3 + "string" + myObject + myObject | - """.trimMargin() + """.trimMargin(), + LintError(2, 10, ruleId, Warnings.STRING_CONCATENATION.warnText() + + " (1 + 2).toString() + \"my string\" + 3 + \"string\" + myObject + myObject", canBeAutoCorrected) ) } @@ -73,7 +77,7 @@ class StringConcatenationWarnTest : LintTestBase(::StringConcatenationRule) { | val a = (1 + 2).toString() + "my string" + ("string" + myObject) + myObject | """.trimMargin(), - LintError(2, 46, ruleId, Warnings.STRING_CONCATENATION.warnText() + + LintError(2, 10, ruleId, Warnings.STRING_CONCATENATION.warnText() + " (1 + 2).toString() + \"my string\" + (\"string\" + myObject) + myObject", canBeAutoCorrected) ) } 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 index c1c7089986..f5fa29987a 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 @@ -148,7 +148,7 @@ class AstNodeUtilsTest { """.trimIndent() val list = listOf("Test", "foo", "a", "a", "Int", "Int", "a") applyToCode(code, 7) { node, counter -> - node.getAllIdentifierChildren().ifNotEmpty { + node.getAllChildrenWithType(IDENTIFIER).ifNotEmpty { this.forEach { Assertions.assertEquals(list[counter.get()], it.text) } counter.incrementAndGet() } @@ -557,7 +557,7 @@ class AstNodeUtilsTest { listResults.add(node) } } - val listTypes = firstNode?.findAllNodesWithSpecificType(IDENTIFIER) + val listTypes = firstNode?.findAllDescendantsWithSpecificType(IDENTIFIER) Assertions.assertEquals(listResults, listTypes) } 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 018d5f5218..1d4896edce 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 @@ -28,7 +28,7 @@ class AvailableRulesDocTest { @Test fun `read rules from documentation`() { - val allRulesFromCode = Warnings.values() + val allRulesFromCode = Warnings.values().filterNot { it == Warnings.DUMMY_TEST_WARNING } val allRulesFromDoc = getAllRulesFromDoc() allRulesFromCode.forEach { warning -> 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 284b7de51a..16f2bfc6eb 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 @@ -27,7 +27,7 @@ class KotlinParserTest { val node = KotlinParser().createNode("val x: Int = 10") Assertions.assertEquals(PROPERTY, node.elementType) Assertions.assertEquals("val x: Int = 10", node.text) - Assertions.assertEquals(4, node.findAllNodesWithSpecificType(WHITE_SPACE).size) + Assertions.assertEquals(4, node.findAllDescendantsWithSpecificType(WHITE_SPACE).size) } @Test @@ -37,7 +37,7 @@ class KotlinParserTest { Assertions.assertEquals(FUN, node.elementType) Assertions.assertEquals("fun foo(text: String) = text.toUpperCase()", node.text) Assertions.assertEquals("foo", node.getIdentifierName()!!.text) - Assertions.assertEquals(4, node.findAllNodesWithSpecificType(WHITE_SPACE).size) + Assertions.assertEquals(4, node.findAllDescendantsWithSpecificType(WHITE_SPACE).size) } @Test 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 58ef54f4ee..5661b44b08 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 @@ -40,7 +40,7 @@ class DiktatRuleSetProviderTest { .filter { it.isFile } .map { it.nameWithoutExtension } .filterNot { it in ignoreFile } - val rulesName = DiktatRuleSetProvider().get().map { it::class.simpleName!! } + val rulesName = DiktatRuleSetProvider().get().map { it::class.simpleName!! }.filter { it != "DummyWarning" } Assertions.assertEquals(filesName.sorted().toList(), rulesName.sorted()) } diff --git a/diktat-rules/src/test/resources/test/paragraph3/string_concatenation/StringConcatenationExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/string_concatenation/StringConcatenationExpected.kt new file mode 100644 index 0000000000..87fc861338 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/string_concatenation/StringConcatenationExpected.kt @@ -0,0 +1,34 @@ +package test.chapter3.strings + +val valueStr = "my str" +val x = 13 + +fun foo(): String { + return "string" +} + +val myTest1 = "my string string $valueStr other value" +val myTest2 = "my string 1$valueStr other value" +val myTest3 = "my string $valueStr string other value" +val myTest4 = "my string one two" +val myTest5 = "trying to sum with multiline" +val myTest6 = "trying to sum with " + """ + multiline""".trimIndent() +val myTest7 = "multiline string" +val myTest8 = "string ${valueStr.replace("my", "")}" +val myTest9 = "string ${"valueStr".replace("my", "")}" +val myTest10 = "string ${(1 + 5)}" +val myTest11 = "sum other string 3 str2 5 other string 2 2 str3 4" +val myTest12 = "my string 123" +val myTest13 = "${(1 + 2)} my string 3 string $valueStr$valueStr" +val myTest14 = "my string ${(1 + 2 + 3)} other string 3${(1 + 2 + 3)}" +val myTest15 = 1 + 2 + ("13").toInt() +val myTest16 = 1.0 + 2.0 + ("13.0").toFloat() +val myTest17 = "sum ${(1 + 2 + 3) * 4}" +val myTest18 = "my string ${(1 + 2 + 3) * 4} other string 3${(1 + (2 + 3))} third string str 5" +val myTest19 = 1 + 2 + 3 + ("65").toInt() +val myTest20 = "${x}string" +val myTest21 = "${x} string" +val myTest22 = "string${foo()}" +val myTest23 = x.toString() + foo() +val myTest24 = foo() + "string" \ No newline at end of file diff --git a/diktat-rules/src/test/resources/test/paragraph3/string_concatenation/StringConcatenationTest.kt b/diktat-rules/src/test/resources/test/paragraph3/string_concatenation/StringConcatenationTest.kt new file mode 100644 index 0000000000..b14425bfa1 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/string_concatenation/StringConcatenationTest.kt @@ -0,0 +1,34 @@ +package test.chapter3.strings + +val valueStr = "my str" +val x = 13 + +fun foo(): String { + return "string" +} + +val myTest1 = "my string " + "string " + valueStr + " other value" +val myTest2 = "my string " + 1 + valueStr + " other value" +val myTest3 = "my string " + valueStr + " string " + "other value" +val myTest4 = "my string" + (" one " + "two") +val myTest5 = "trying to sum with " + """multiline""" +val myTest6 = "trying to sum with " + """ + multiline""".trimIndent() +val myTest7 = """multiline""" + " string" +val myTest8 = "string " + valueStr.replace("my", "") +val myTest9 = "string " + "valueStr".replace("my", "") +val myTest10 = "string " + (1 + 5) +val myTest11 = "sum " + ("other string " + 3 + " str2 " + 5 + (" other string 2 " + 2 + " str3 " + 4)) +val myTest12 = "my string " + 1 + 2 + 3 +val myTest13 = (1 + 2).toString() + " my string " + 3 + " string " + valueStr + valueStr +val myTest14 = "my string " + (1 + 2 + 3) + (" other string " + 3) + (1 + 2 + 3) +val myTest15 = 1 + 2 + ("1" + 3).toInt() +val myTest16 = 1.0 + 2.0 + ("1" + 3.0).toFloat() +val myTest17 = "sum " + (1 + 2 + 3) * 4 +val myTest18 = "my string " + (1 + 2 + 3) * 4 + (" other string " + 3) + (1 + (2 + 3)) + (" third string " + ("str " + 5)) +val myTest19 = 1 + 2 + 3 + ("6" + 5).toInt() +val myTest20 = x.toString() + "string" +val myTest21 = x.toString() + " string" +val myTest22 = "string" + foo() +val myTest23 = x.toString() + foo() +val myTest24 = foo() + "string" diff --git a/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/WarningsTableGenerator.kt b/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/WarningsTableGenerator.kt index b29209c058..a84210621e 100644 --- a/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/WarningsTableGenerator.kt +++ b/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/WarningsTableGenerator.kt @@ -15,7 +15,8 @@ const val DIKTAT_GUIDE: String = "guide/diktat-coding-convention.md#" */ @Suppress("MagicNumber") fun generateRulesMapping() { - val allWarnings = Warnings.values() + // excluding dummy warning + val allWarnings = Warnings.values().filterNot { it == Warnings.DUMMY_TEST_WARNING } allWarnings.sortBy { warn -> val numbers = warn.ruleId.split(".") val chapter = numbers[0].toInt()