diff --git a/diktat-rules/pom.xml b/diktat-rules/pom.xml index d4bd8db61d..8835182240 100644 --- a/diktat-rules/pom.xml +++ b/diktat-rules/pom.xml @@ -65,6 +65,11 @@ assertj-core test + + org.mockito + mockito-all + test + diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ImmutableValNoVarRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ImmutableValNoVarRule.kt index e169d74e7c..71d7fc4506 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ImmutableValNoVarRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ImmutableValNoVarRule.kt @@ -4,13 +4,12 @@ import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.ast.ElementType import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.SAY_NO_TO_VAR -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType -import org.cqfn.diktat.ruleset.utils.getAllUsages +import org.cqfn.diktat.ruleset.utils.search.findAllVariablesWithAssignments +import org.cqfn.diktat.ruleset.utils.search.findAllVariablesWithUsages import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.psi.KtBlockExpression import org.jetbrains.kotlin.psi.KtLambdaExpression import org.jetbrains.kotlin.psi.KtLoopExpression -import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.psiUtil.getParentOfType /** @@ -29,22 +28,33 @@ class ImmutableValNoVarRule(private val configRules: List) : Rule(" emitWarn = emit isFixMode = autoCorrect - if (node.elementType == ElementType.FUN) { - node.findAllNodesWithSpecificType(ElementType.PROPERTY) - .map { it.psi as KtProperty } - // we can force to be immutable only variables that are from local context (not from class and not from file-level) - .filter { it.isLocal && it.name != null && it.parent is KtBlockExpression } - .filter { it.isVar } - .forEach { property -> - val usedInAccumulators = property.getAllUsages().any { - it.getParentOfType(true) != null || - it.getParentOfType(true) != null - } - - if (!usedInAccumulators) { - SAY_NO_TO_VAR.warn(configRules, emitWarn, isFixMode, property.text, property.node.startOffset, property.node) - } - } + if (node.elementType == ElementType.FILE) { + // we will raise warning for cases when var property has no assignments + val varNoAssignments = node + .findAllVariablesWithAssignments { it.name != null && it.isVar } + .filter { it.value.isEmpty() } + + varNoAssignments.forEach { (property, usages) -> + // FixMe: raise another warning and fix the code (change to val) for variables without assignment + } + + // we can force to be immutable only variables that are from local context (not from class and not from file-level) + val usages = node + .findAllVariablesWithUsages { it.isLocal && it.name != null && it.parent is KtBlockExpression && it.isVar } + .filter { !varNoAssignments.containsKey(it.key) } + + usages.forEach { (property, usages) -> + val usedInAccumulators = usages.any { + it.getParentOfType(true) != null || + it.getParentOfType(true) != null + } + + if (!usedInAccumulators) { + SAY_NO_TO_VAR.warn(configRules, emitWarn, isFixMode, property.text, property.node.startOffset, property.node) + } + } + + return } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/FileSize.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/FileSize.kt index 6c7b056646..6aa690ee4d 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/FileSize.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/FileSize.kt @@ -45,6 +45,7 @@ class FileSize(private val configRules: List) : Rule("file-size") { checkFileSize(node, configuration.maxSize) } } + return } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/FileStructureRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/FileStructureRule.kt index 1d507d2c69..86f0661542 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/FileStructureRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/FileStructureRule.kt @@ -59,6 +59,7 @@ class FileStructureRule(private val configRules: List) : Rule("file if (checkFileHasCode(node)) { checkCodeBlocksOrderAndEmptyLines(node) } + return } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/identifiers/LocalVariablesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/identifiers/LocalVariablesRule.kt index 1ad3c50690..6a105e2058 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/identifiers/LocalVariablesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/identifiers/LocalVariablesRule.kt @@ -2,16 +2,14 @@ package org.cqfn.diktat.ruleset.rules.identifiers import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.ast.ElementType.FILE -import com.pinterest.ktlint.core.ast.ElementType.PROPERTY import com.pinterest.ktlint.core.ast.isPartOfComment import com.pinterest.ktlint.core.ast.lineNumber import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.LOCAL_VARIABLE_EARLY_DECLARATION import org.cqfn.diktat.ruleset.utils.containsOnlyConstants -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType -import org.cqfn.diktat.ruleset.utils.getAllUsages import org.cqfn.diktat.ruleset.utils.getDeclarationScope import org.cqfn.diktat.ruleset.utils.lastLineNumber +import org.cqfn.diktat.ruleset.utils.search.findAllVariablesWithUsages import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiElement import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace @@ -57,7 +55,7 @@ class LocalVariablesRule(private val configRules: List) : Rule("loc if (node.elementType == FILE) { // collect all local properties and associate with corresponding references - val propertiesToUsages = collectPropertiesWithUsages(node) + val propertiesToUsages = collectLocalPropertiesWithUsages(node) // find all usages which include more than one property val multiPropertyUsages = groupPropertiesByUsages(propertiesToUsages) @@ -73,16 +71,14 @@ class LocalVariablesRule(private val configRules: List) : Rule("loc } } - private fun collectPropertiesWithUsages(node: ASTNode) = node - .findAllNodesWithSpecificType(PROPERTY) - .map { it.psi as KtProperty } - .filter { it.isLocal && it.name != null && it.parent is KtBlockExpression } - .filter { - it.isVar && it.initializer == null || - (it.initializer?.containsOnlyConstants() ?: false) || - (it.initializer as? KtCallExpression).isWhitelistedMethod() + private fun collectLocalPropertiesWithUsages(node: ASTNode) = node + .findAllVariablesWithUsages { propertyNode -> + propertyNode.isLocal && propertyNode.name != null && propertyNode.parent is KtBlockExpression && + (propertyNode.isVar && propertyNode.initializer == null || + (propertyNode.initializer?.containsOnlyConstants() ?: false) || + (propertyNode.initializer as? KtCallExpression).isWhitelistedMethod()) } - .associateWith { it.getAllUsages() } + .filterNot { it.value.isEmpty() } private fun groupPropertiesByUsages(propertiesToUsages: Map>) = propertiesToUsages 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 4c936a8bf5..d267aa1dc1 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 @@ -138,7 +138,6 @@ fun ASTNode.findChildBefore(beforeThisNodeType: IElementType, childNodeType: IEl .find { it.elementType == childNodeType } ?.let { return it } - log.warn("Not able to find a node with type $childNodeType before $beforeThisNodeType") return null } @@ -160,7 +159,6 @@ fun ASTNode.findChildAfter(afterThisNodeType: IElementType, childNodeType: IElem } } - log.warn("Not able to find a node with type $childNodeType after $afterThisNodeType") return null } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/VariableSearchASTUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/VariableSearchASTUtils.kt deleted file mode 100644 index 4dc7be6e7c..0000000000 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/VariableSearchASTUtils.kt +++ /dev/null @@ -1,108 +0,0 @@ -package org.cqfn.diktat.ruleset.utils - -import com.pinterest.ktlint.core.ast.ElementType -import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.psi.KtClassBody -import org.jetbrains.kotlin.psi.KtDotQualifiedExpression -import org.jetbrains.kotlin.psi.KtNameReferenceExpression -import org.jetbrains.kotlin.psi.KtProperty -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtBlockExpression -import org.jetbrains.kotlin.psi.KtElement -import org.jetbrains.kotlin.psi.KtFunctionLiteral -import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType -import org.jetbrains.kotlin.psi.psiUtil.getParentOfType -import org.jetbrains.kotlin.psi.psiUtil.parents -import org.jetbrains.kotlin.psi.psiUtil.referenceExpression - -/** - * [this] - root node of a type File that is used to search all declared properties (variables) - * and it's usages (rvalues). - * (!) ONLY for nodes of File elementType - * - * @return a map of a property to it's usages - */ -fun ASTNode.collectAllDeclaredVariablesWithUsages(): Map> { - require(this.elementType == ElementType.FILE) { - "To collect all variables in a file you need to provide file root node" - } - - return this - .findAllNodesWithSpecificType(ElementType.PROPERTY) - .map { it.psi as KtProperty } - .associateWith { it.getAllUsages() } -} - -/** - * Finds all references to [this] in the same code block. - * [this] - usages of this property will be searched - * @return list of references as [KtNameReferenceExpression] - */ -@Suppress("UnsafeCallOnNullableType") -fun KtProperty.getAllUsages(): List { - return this - .getDeclarationScope() - // if declaration scope is not null - then we have found out the block where this variable is stored - // else - it is a global variable on a file level or a property on the class level - .let { declarationScope -> - // searching in the scope with declaration (in the context) - declarationScope?.getAllUsagesOfProperty(this) - // searching on the class level in class body - ?: (this.getParentOfType(true)?.getAllUsagesOfProperty(this)) - // searching on the file level - ?: (this.getParentOfType(true)!!.getAllUsagesOfProperty(this)) - } -} - -/** - * getting all usages of a variable inside the same (or nested) block (where variable was declared) - */ -private fun KtElement.getAllUsagesOfProperty(property: KtProperty) = - this.node.findAllNodesWithSpecificType(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 } - .filter { it.getReferencedNameAsName() == property.nameAsName } - .filterNot { expression -> - // to avoid false triggering on objects' fields with same name as property - isReferenceToFieldOfObject(expression) || - // to exclude usages of local properties from other context (shadowed) and lambda arguments with same name - isReferenceToOtherVariableWithSameName(expression, this, property) - } - -/** - * filtering object's fields (expressions) that have same name as variable - */ -private fun isReferenceToFieldOfObject(expression: KtNameReferenceExpression) = - (expression.parent as? KtDotQualifiedExpression)?.run { - receiverExpression != expression && selectorExpression?.referenceExpression() == expression - } ?: false - - -/** - * filtering local properties from other context (shadowed) and lambda and function arguments with same name - * going through all parent scopes from bottom to top until we will find the scope where the initial variable was declared - * all these scopes are on lower level of inheritance that's why if in one of these scopes we will find any - * variable declaration with the same name - we will understand that it is usage of another variable -*/ -private fun isReferenceToOtherVariableWithSameName(expression: KtNameReferenceExpression, codeBlock: KtElement, property: KtProperty): Boolean { - return expression.parents - // getting all block expressions/class bodies/file node from bottom to the top - // FixMe: Object companion is not resolved properly yet - .filter { it is KtBlockExpression || it is KtClassBody || it is KtFile } - // until we reached the block that contains the initial declaration - .takeWhile { codeBlock != it } - .any { block -> - // this is not the expression that we needed if: - // 1) there is a new shadowed declaration for this expression (but the declaration should stay on the previous line!) - // 2) or there one of top blocks is a function/lambda that has arguments with the same name - // FixMe: in class or a file the declaration can easily go after the usage (by lines of code) - block.getChildrenOfType().any { it.nameAsName == property.nameAsName && expression.node.isGoingAfter(it.node) } || - block.parent - .let { it as? KtFunctionLiteral } - ?.valueParameters - ?.any { it.nameAsName == property.nameAsName } - ?: false - // FixMe: also see very strange behavior of Kotlin in tests (disabled) - } -} 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 new file mode 100644 index 0000000000..d98a41ead9 --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesSearch.kt @@ -0,0 +1,110 @@ +package org.cqfn.diktat.ruleset.utils.search + +import com.pinterest.ktlint.core.ast.ElementType +import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.getDeclarationScope +import org.cqfn.diktat.ruleset.utils.isGoingAfter +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.psi.KtClassBody +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtNameReferenceExpression +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtFunctionLiteral +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.psi.psiUtil.parents +import org.jetbrains.kotlin.psi.psiUtil.referenceExpression + +/** + * @param node - root node of a type File that is used to search all declared properties (variables) + * it should be ONLY node of File elementType + * @param filterForVariables - condition to filter + */ +abstract class VariablesSearch(val node: ASTNode, private val filterForVariables: (KtProperty) -> Boolean) { + + /** + * to complete implementation of a search mechanism you need to specify what and how you will search in current scope + * [this] - scope where to search the usages/assignments/e.t.c of the variable (can be of types KtBlockExpression/KtFile/KtClassBody) + */ + protected abstract fun KtElement.getAllSearchResults(property: KtProperty): List + + /** + * method collects all declared variables and it's usages + * + * @return a map of a property to it's usages + */ + fun collectVariables(): Map> { + require(node.elementType == ElementType.FILE) { + "To collect all variables in a file you need to provide file root node" + } + return node + .findAllNodesWithSpecificType(ElementType.PROPERTY) + .map { it.psi as KtProperty } + .filter(filterForVariables) + .associateWith { it.getSearchResults() } + } + + @Suppress("UnsafeCallOnNullableType") + fun KtProperty.getSearchResults(): List { + return this + .getDeclarationScope() + // if declaration scope is not null - then we have found out the block where this variable is stored + // else - it is a global variable on a file level or a property on the class level + .let { declarationScope -> + // searching in the scope with declaration (in the context) + declarationScope?.getAllSearchResults(this) + // searching on the class level in class body + ?: (this.getParentOfType(true)?.getAllSearchResults(this)) + // searching on the file level + ?: (this.getParentOfType(true)!!.getAllSearchResults(this)) + } + } + + /** + * filtering object's fields (expressions) that have same name as variable + */ + protected fun KtNameReferenceExpression.isReferenceToFieldOfObject(): Boolean { + val expression = this + return (expression.parent as? KtDotQualifiedExpression)?.run { + receiverExpression != expression && selectorExpression?.referenceExpression() == expression + } ?: false + } + + /** + * filtering local properties from other context (shadowed) and lambda and function arguments with same name + * going through all parent scopes from bottom to top until we will find the scope where the initial variable was declared + * all these scopes are on lower level of inheritance that's why if in one of these scopes we will find any + * variable declaration with the same name - we will understand that it is usage of another variable + */ + protected fun isReferenceToOtherVariableWithSameName(expression: KtNameReferenceExpression, + codeBlock: KtElement, property: KtProperty): Boolean { + return expression.parents + // getting all block expressions/class bodies/file node from bottom to the top + // FixMe: Object companion is not resolved properly yet + .filter { it is KtBlockExpression || it is KtClassBody || it is KtFile } + // until we reached the block that contains the initial declaration + .takeWhile { codeBlock != it } + .any { block -> + // this is not the expression that we needed if: + // 1) there is a new shadowed declaration for this expression (but the declaration should stay on the previous line!) + // 2) or there one of top blocks is a function/lambda that has arguments with the same name + // FixMe: in class or a file the declaration can easily go after the usage (by lines of code) + block.getChildrenOfType().any { it.nameAsName == property.nameAsName && expression.node.isGoingAfter(it.node) } || + block.parent + .let { it as? KtFunctionLiteral } + ?.valueParameters + ?.any { it.nameAsName == property.nameAsName } + ?: false + // FixMe: also see very strange behavior of Kotlin in tests (disabled) + } + } +} + +/** + * this is a small workaround in case we don't want to make any custom filter while searching variables + */ +@SuppressWarnings("FunctionOnlyReturningConstant") +fun default(node: KtProperty) = true 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 new file mode 100644 index 0000000000..29a24947a3 --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithAssignmentSearch.kt @@ -0,0 +1,45 @@ +package org.cqfn.diktat.ruleset.utils.search + +import com.pinterest.ktlint.core.ast.ElementType +import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.isGoingAfter +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtNameReferenceExpression +import org.jetbrains.kotlin.psi.KtProperty + +class VariablesWithAssignmentSearch(fileNode: ASTNode, + filterForVariables: (KtProperty) -> Boolean) : VariablesSearch(fileNode, filterForVariables) { + + /** + * searching for all assignments of variables in current context [this] + */ + override fun KtElement.getAllSearchResults(property: KtProperty): List { + return this.node.findAllNodesWithSpecificType(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 { + // FixMe: bug is here with a search of postfix/prefix variables assignment (like ++). + // FixMe: Currently we check only val a = 5, ++a is not checked here + // FixMe: also there can be some tricky cases with setters, but I am not able to imagine them now + it.isGoingAfter(property.node) && + (it.psi as KtBinaryExpression).operationToken == ElementType.EQ && + (it.psi as KtBinaryExpression).left?.node?.elementType == ElementType.REFERENCE_EXPRESSION + } + .map { (it.psi as KtBinaryExpression).left as KtNameReferenceExpression } + // checking that name of the property in usage matches with the name in the declaration + .filter { it.getReferencedNameAsName() == property.nameAsName } + .filterNot { expression -> + // to avoid false triggering on objects' fields with same name as property + expression.isReferenceToFieldOfObject() || + // to exclude usages of local properties from other context (shadowed) and lambda arguments with same name + isReferenceToOtherVariableWithSameName(expression, this, property) + } + .toList() + } +} + +// the default value for filtering condition is always true +fun ASTNode.findAllVariablesWithAssignments(filterForVariables: (KtProperty) -> Boolean = ::default) = + VariablesWithAssignmentSearch(this, filterForVariables).collectVariables() 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 new file mode 100644 index 0000000000..2422a88bc8 --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithUsagesSearch.kt @@ -0,0 +1,32 @@ +package org.cqfn.diktat.ruleset.utils.search + +import com.pinterest.ktlint.core.ast.ElementType +import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.isGoingAfter +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtNameReferenceExpression +import org.jetbrains.kotlin.psi.KtProperty + +class VariablesWithUsagesSearch(fileNode: ASTNode, + filterForVariables: (KtProperty) -> Boolean) : VariablesSearch(fileNode, filterForVariables) { + + override fun KtElement.getAllSearchResults(property: KtProperty): List { + return this.node.findAllNodesWithSpecificType(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 } + .filter { it.getReferencedNameAsName() == property.nameAsName } + .filterNot { expression -> + // to avoid false triggering on objects' fields with same name as property + expression.isReferenceToFieldOfObject() || + // to exclude usages of local properties from other context (shadowed) and lambda arguments with same name + isReferenceToOtherVariableWithSameName(expression, this, property) + } + } +} + +// the default value for filtering condition is always true +fun ASTNode.findAllVariablesWithUsages(filterForVariables: (KtProperty) -> Boolean = ::default) = + VariablesWithUsagesSearch(this, filterForVariables).collectVariables() + diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter4/NoVarRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter4/NoVarRuleWarnTest.kt index cf607b585b..69f16d4c43 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter4/NoVarRuleWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter4/NoVarRuleWarnTest.kt @@ -35,6 +35,7 @@ class NoVarRuleWarnTest : LintTestBase(::ImmutableValNoVarRule) { """ | fun foo() { | var a = emptyList() + | a = 15 | var y = 0 | a.forEach { x -> | y = x + 1 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 new file mode 100644 index 0000000000..8b1430039e --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesSearchTest.kt @@ -0,0 +1,46 @@ +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 org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.mockito.Mockito + +@Suppress("UnsafeCallOnNullableType") +class VariablesSearchTest { + @Test + fun `testing requirement for collecting variables`() { + applyToCode(""" + fun foo(a: Int) { + fun foo1() { + var o = 1 + b = o + c = o + o = 15 + o = 17 + } + } + """.trimIndent(), 0) { node, counter -> + if (node.elementType != ElementType.FILE) { + val thrown = Assertions.assertThrows(IllegalArgumentException::class.java) { + val variablesSearchAbstract: VariablesSearch = Mockito.mock(VariablesSearch::class.java, Mockito.CALLS_REAL_METHODS) + val nodeField = VariablesSearch::class.java.getDeclaredField("node") + val filter = VariablesSearch::class.java.getDeclaredField("filterForVariables") + nodeField.isAccessible = true + filter.isAccessible = true + + nodeField.set(variablesSearchAbstract, node) + filter.set(variablesSearchAbstract, ::default) + + variablesSearchAbstract.collectVariables() + } + 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 new file mode 100644 index 0000000000..dca992f238 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithAssignmentsSearchTest.kt @@ -0,0 +1,79 @@ +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 org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test + +@Suppress("UnsafeCallOnNullableType") +class VariablesWithAssignmentsSearchTest { + @Test + fun `testing proper variables search in function`() { + applyToCode(""" + fun foo(a: Int) { + fun foo1() { + var o = 1 + b = o + c = o + o = 15 + o = 17 + } + } + """.trimIndent(), 0) { node, counter -> + if (node.elementType == FILE) { + val vars = node.findAllVariablesWithAssignments().mapKeys { it.key.text } + val keys = vars.keys + val var1 = keys.elementAt(0) + Assertions.assertEquals("var o = 1", var1) + Assertions.assertEquals(2, vars[var1]?.size) + } + } + } + + @Test + fun `testing proper variables search in class`() { + applyToCode(""" + class A { + var o = 1 + fun foo(a: Int) { + fun foo1() { + b = o + c = o + d = o + o = 15 + o = 17 + } + } + } + """.trimIndent(), 0) { node, counter -> + if (node.elementType == FILE) { + val vars = node.findAllVariablesWithAssignments().mapKeys { it.key.text } + val keys = vars.keys + val var1 = keys.elementAt(0) + Assertions.assertEquals("var o = 1", var1) + Assertions.assertEquals(2, vars[var1]?.size) + } + } + } + + @Test + @Disabled + fun `testing proper variables search with lambda`() { + applyToCode(""" + fun foo(a: Int) { + var a = 1 + a++ + } + """.trimIndent(), 0) { node, counter -> + if (node.elementType == FILE) { + val vars = node.findAllVariablesWithAssignments().mapKeys { it.key.text } + val keys = vars.keys + val var1 = keys.elementAt(0) + Assertions.assertEquals("var a = 1", var1) + Assertions.assertEquals(1, vars[var1]?.size) + } + } + } +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariableSearchASTUtilsTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithUsagesSearchTest.kt similarity index 88% rename from diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariableSearchASTUtilsTest.kt rename to diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithUsagesSearchTest.kt index 7080144c76..ee45294e9f 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariableSearchASTUtilsTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithUsagesSearchTest.kt @@ -1,13 +1,14 @@ 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 org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test @Suppress("UnsafeCallOnNullableType") -class VariableSearchASTUtilsTest { +class VariablesWithUsagesSearchTest { @Test fun `testing proper variables search in function`() { applyToCode(""" @@ -20,7 +21,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) val var2 = keys.elementAt(1) @@ -44,7 +45,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) val var2 = keys.elementAt(1) @@ -70,7 +71,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) val var2 = keys.elementAt(1) @@ -94,7 +95,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) assertEquals("val v = 0", var1) @@ -119,7 +120,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) val var2 = keys.elementAt(1) @@ -142,7 +143,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) assertEquals("val someVal = 0", var1) @@ -163,7 +164,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) val var2 = keys.elementAt(1) @@ -188,7 +189,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) assertEquals("var a = 5", var1) @@ -208,7 +209,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) assertEquals("var a = 5", var1) @@ -230,7 +231,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) val var2 = keys.elementAt(1) @@ -260,7 +261,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) val var2 = keys.elementAt(1) @@ -292,7 +293,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) val var2 = keys.elementAt(1) @@ -326,7 +327,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) val var2 = keys.elementAt(1) @@ -358,7 +359,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) assertEquals("var v = 1", var1) @@ -380,7 +381,7 @@ class VariableSearchASTUtilsTest { } """.trimIndent(), 0) { node, counter -> if (node.elementType == FILE) { - val vars = node.collectAllDeclaredVariablesWithUsages().mapKeys { it.key.text } + val vars = node.findAllVariablesWithUsages().mapKeys { it.key.text } val keys = vars.keys val var1 = keys.elementAt(0) @@ -389,5 +390,4 @@ class VariableSearchASTUtilsTest { } } } - } diff --git a/pom.xml b/pom.xml index 8d9b81efa1..f6a529ad14 100644 --- a/pom.xml +++ b/pom.xml @@ -163,7 +163,14 @@ assertj-core 3.17.0 + + org.mockito + mockito-all + 1.10.19 + test + +