-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rule 6.1.4 several init blocks are redundant and generally should not…
… be used in your class (#459) ### What's done: * Implementation * Fixed typos in code style * paragraph6 -> chapter6
- Loading branch information
Showing
23 changed files
with
303 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package org.cqfn.diktat.ruleset.rules.classes | ||
|
||
import com.pinterest.ktlint.core.Rule | ||
import com.pinterest.ktlint.core.ast.ElementType.BLOCK | ||
import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY | ||
import com.pinterest.ktlint.core.ast.ElementType.CLASS_INITIALIZER | ||
import com.pinterest.ktlint.core.ast.ElementType.EQ | ||
import com.pinterest.ktlint.core.ast.ElementType.PROPERTY | ||
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE | ||
import com.pinterest.ktlint.core.ast.parent | ||
import org.cqfn.diktat.common.config.rules.RulesConfig | ||
import org.cqfn.diktat.ruleset.constants.Warnings | ||
import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType | ||
import org.cqfn.diktat.ruleset.utils.getIdentifierName | ||
import org.jetbrains.kotlin.backend.common.onlyIf | ||
import org.jetbrains.kotlin.com.intellij.lang.ASTNode | ||
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement | ||
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl | ||
import org.jetbrains.kotlin.psi.KtBlockExpression | ||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression | ||
import org.jetbrains.kotlin.psi.KtProperty | ||
import org.jetbrains.kotlin.psi.psiUtil.asAssignment | ||
import org.jetbrains.kotlin.psi.psiUtil.children | ||
|
||
class SingleInitRule(private val configRule: List<RulesConfig>) : Rule("multiple-init-block") { | ||
private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) | ||
private var isFixMode: Boolean = false | ||
|
||
override fun visit( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit | ||
) { | ||
emitWarn = emit | ||
isFixMode = autoCorrect | ||
|
||
when (node.elementType) { | ||
CLASS_BODY -> handleInitBlocks(node) | ||
} | ||
} | ||
|
||
private fun handleInitBlocks(node: ASTNode) { | ||
// merge init blocks if there are multiple | ||
node.children() | ||
.filter { it.elementType == CLASS_INITIALIZER } | ||
.toList() | ||
.onlyIf({ size > 1 }) { initBlocks -> | ||
val className = node.treeParent.getIdentifierName()?.text | ||
Warnings.MULTIPLE_INIT_BLOCKS.warnAndFix(configRule, emitWarn, isFixMode, | ||
"in class <$className> found ${initBlocks.size} `init` blocks", node.startOffset, node) { | ||
mergeInitBlocks(initBlocks) | ||
} | ||
} | ||
|
||
// move property assignments from init block to property declarations | ||
node.findChildByType(CLASS_INITIALIZER)?.let { initBlock -> | ||
val properties = node | ||
.children() | ||
.filter { it.elementType == PROPERTY } | ||
.toList() | ||
moveAssignmentsToProperties(properties, initBlock) | ||
} | ||
} | ||
|
||
private fun mergeInitBlocks(initBlocks: List<ASTNode>) { | ||
val firstInitBlock = initBlocks.first() | ||
initBlocks.drop(1).forEach { initBlock -> | ||
firstInitBlock.findChildByType(BLOCK)?.run { | ||
val beforeNode = lastChildNode.treePrev.takeIf { it.elementType == WHITE_SPACE } ?: lastChildNode | ||
(initBlock.findChildByType(BLOCK)?.psi as? KtBlockExpression)?.statements?.forEach { | ||
addChild(PsiWhiteSpaceImpl("\n"), beforeNode) | ||
addChild(it.node.clone() as ASTNode, beforeNode) | ||
} | ||
} | ||
if (initBlock.treePrev.elementType == WHITE_SPACE && initBlock.treeNext.elementType == WHITE_SPACE) { | ||
initBlock.treeParent.removeChild(initBlock.treeNext) | ||
} | ||
initBlock.treeParent.removeChild(initBlock) | ||
} | ||
firstInitBlock.parent(CLASS_BODY)?.let(::removeEmptyBlocks) | ||
} | ||
|
||
@Suppress("UnsafeCallOnNullableType") | ||
private fun moveAssignmentsToProperties(properties: List<ASTNode>, initBlock: ASTNode) { | ||
initBlock | ||
.findChildByType(BLOCK) | ||
?.run { | ||
(psi as KtBlockExpression).statements | ||
.mapNotNull { it.asAssignment() } | ||
.filter { it.left is KtNameReferenceExpression } | ||
.groupBy { assignment -> | ||
val assignedRef = assignment.left as KtNameReferenceExpression | ||
properties.find { (it.psi as KtProperty).name == assignedRef.getReferencedName() } | ||
} | ||
.filterKeys { it != null } | ||
.mapKeys { (k, _) -> k as ASTNode } | ||
.filter { (property, assignments) -> | ||
!(property.psi as KtProperty).hasBody() && assignments.size == 1 | ||
} | ||
.onlyIf({ isNotEmpty() }) { | ||
Warnings.MULTIPLE_INIT_BLOCKS.warnAndFix(configRule, emitWarn, isFixMode, | ||
"`init` block has assignments that can be moved to declarations", initBlock.startOffset, initBlock | ||
) { | ||
it.forEach { (property, assignments) -> | ||
val assignment = assignments.single() | ||
property.addChild(PsiWhiteSpaceImpl(" "), null) | ||
property.addChild(LeafPsiElement(EQ, "="), null) | ||
property.addChild(PsiWhiteSpaceImpl(" "), null) | ||
property.addChild(assignment.right!!.node.clone() as ASTNode, null) | ||
assignment.node.run { | ||
if (treePrev.elementType == WHITE_SPACE) { | ||
treeParent.removeChild(treePrev) | ||
} | ||
treeParent.removeChild(this) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
initBlock.parent(CLASS_BODY)?.let(::removeEmptyBlocks) | ||
} | ||
|
||
private fun removeEmptyBlocks(node: ASTNode) { | ||
node.getAllChildrenWithType(CLASS_INITIALIZER) | ||
.filter { | ||
(it.findChildByType(BLOCK)?.psi as KtBlockExpression?)?.statements?.isEmpty() ?: false | ||
} | ||
.forEach { | ||
it.treeParent.removeChild(it) | ||
} | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
...fn/diktat/ruleset/rules/WhiteSpaceRule.kt → ...tat/ruleset/rules/files/WhiteSpaceRule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...at-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/WhiteSpaceRuleFixTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 1 addition & 2 deletions
3
diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/AbstractClassesFixTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/SingleInitRuleFixTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package org.cqfn.diktat.ruleset.chapter6 | ||
|
||
import generated.WarningNames | ||
import org.cqfn.diktat.util.FixTestBase | ||
import org.cqfn.diktat.ruleset.rules.classes.SingleInitRule | ||
import org.junit.jupiter.api.Tag | ||
import org.junit.jupiter.api.Test | ||
|
||
class SingleInitRuleFixTest: FixTestBase("test/chapter6/init_blocks", ::SingleInitRule) { | ||
@Test | ||
@Tag(WarningNames.MULTIPLE_INIT_BLOCKS) | ||
fun `should merge init blocks`() { | ||
fixAndCompare("InitBlocksExpected.kt", "InitBlocksTest.kt") | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.MULTIPLE_INIT_BLOCKS) | ||
fun `should move property assignments from init blocks to declarations`() { | ||
fixAndCompare("InitBlockWithAssignmentsExpected.kt", "InitBlockWithAssignmentsTest.kt") | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.MULTIPLE_INIT_BLOCKS) | ||
fun `should merge init blocks and move property assignments from init blocks to declarations`() { | ||
fixAndCompare("InitBlocksWithAssignmentsExpected.kt", "InitBlocksWithAssignmentsTest.kt") | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/SingleInitRuleWarnTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package org.cqfn.diktat.ruleset.chapter6 | ||
|
||
import com.pinterest.ktlint.core.LintError | ||
import generated.WarningNames | ||
import org.cqfn.diktat.ruleset.constants.Warnings | ||
import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID | ||
import org.cqfn.diktat.ruleset.rules.classes.SingleInitRule | ||
import org.cqfn.diktat.util.LintTestBase | ||
import org.junit.jupiter.api.Tag | ||
import org.junit.jupiter.api.Test | ||
|
||
class SingleInitRuleWarnTest: LintTestBase(::SingleInitRule) { | ||
private val ruleId = "$DIKTAT_RULE_SET_ID:multiple-init-block" | ||
|
||
@Test | ||
@Tag(WarningNames.MULTIPLE_INIT_BLOCKS) | ||
fun `should allow single init block`() { | ||
lintMethod( | ||
""" | ||
|class Example { | ||
| init { println("Lorem ipsum") } | ||
|} | ||
""".trimMargin() | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.MULTIPLE_INIT_BLOCKS) | ||
fun `should forbid multiple init blocks`() { | ||
lintMethod( | ||
""" | ||
|class Example { | ||
| init { println("Lorem ipsum") } | ||
| | ||
| val foo = 0 | ||
| | ||
| init { println("Dolor sit amet") } | ||
|} | ||
""".trimMargin(), | ||
LintError(1, 15, ruleId, "${Warnings.MULTIPLE_INIT_BLOCKS.warnText()} in class <Example> found 2 `init` blocks", true) | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.MULTIPLE_INIT_BLOCKS) | ||
fun `should warn if properties are assigned in init block`() { | ||
lintMethod( | ||
""" | ||
|class A(baseUrl: String) { | ||
| private val customUrl: String | ||
| init { | ||
| customUrl = "${'$'}baseUrl/myUrl" | ||
| } | ||
|} | ||
""".trimMargin(), | ||
LintError(3, 5, ruleId, "${Warnings.MULTIPLE_INIT_BLOCKS.warnText()} `init` block has assignments that can be moved to declarations", true) | ||
) | ||
} | ||
} |
File renamed without changes.
File renamed without changes.
6 changes: 6 additions & 0 deletions
6
...at-rules/src/test/resources/test/chapter6/init_blocks/InitBlockWithAssignmentsExpected.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package test.chapter6.init_blocks | ||
|
||
class A(baseUrl: String) { | ||
private val customUrl: String = "$baseUrl/myUrl" | ||
|
||
} |
8 changes: 8 additions & 0 deletions
8
diktat-rules/src/test/resources/test/chapter6/init_blocks/InitBlockWithAssignmentsTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package test.chapter6.init_blocks | ||
|
||
class A(baseUrl: String) { | ||
private val customUrl: String | ||
init { | ||
customUrl = "$baseUrl/myUrl" | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
diktat-rules/src/test/resources/test/chapter6/init_blocks/InitBlocksExpected.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package test.chapter6.init_blocks | ||
|
||
class Example { | ||
init { println("Lorem ipsum") | ||
println("Dolor sit amet") } | ||
|
||
val foo = 0 | ||
|
||
} |
9 changes: 9 additions & 0 deletions
9
diktat-rules/src/test/resources/test/chapter6/init_blocks/InitBlocksTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package test.chapter6.init_blocks | ||
|
||
class Example { | ||
init { println("Lorem ipsum") } | ||
|
||
val foo = 0 | ||
|
||
init { println("Dolor sit amet") } | ||
} |
10 changes: 10 additions & 0 deletions
10
...t-rules/src/test/resources/test/chapter6/init_blocks/InitBlocksWithAssignmentsExpected.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package test.chapter6.init_blocks | ||
|
||
class A(baseUrl: String) { | ||
private val customUrl: String = "$baseUrl/myUrl" | ||
init { | ||
println("Lorem ipsum") | ||
println("Dolor sit amet") | ||
} | ||
|
||
} |
Oops, something went wrong.
fa9a4fc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't able to retrieve PDD puzzles from the code base and submit them to GitHub. If you think that it's a bug on our side, please submit it to yegor256/0pdd:
Please, copy and paste this stack trace to GitHub: