Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect fix of null-safety rule #857

Merged
merged 12 commits into from
Apr 30, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,9 @@ enum class Warnings(
canBeAutoCorrected: Boolean = this.canBeAutoCorrected,
autoFix: () -> Unit) {
warn(configRules, emit, canBeAutoCorrected, freeText, offset, node)
fix(configRules, isFixMode, node, autoFix)
if (canBeAutoCorrected) {
fix(configRules, isFixMode, node, autoFix)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import org.cqfn.diktat.ruleset.utils.*

import com.pinterest.ktlint.core.ast.ElementType
import com.pinterest.ktlint.core.ast.ElementType.BINARY_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.BLOCK
import com.pinterest.ktlint.core.ast.ElementType.BREAK
import com.pinterest.ktlint.core.ast.ElementType.CONDITION
import com.pinterest.ktlint.core.ast.ElementType.ELSE
import com.pinterest.ktlint.core.ast.ElementType.IF
Expand All @@ -21,6 +23,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.util.collectionUtils.concat

/**
* This rule check and fixes explicit null checks (explicit comparison with `null`)
Expand Down Expand Up @@ -67,14 +70,20 @@ class NullChecksRule(configRules: List<RulesConfig>) : DiktatRule(
when (condition.operationToken) {
// `==` and `===` comparison can be fixed with `?:` operator
ElementType.EQEQ, ElementType.EQEQEQ ->
warnAndFixOnNullCheck(condition, true,
"use '.let/.also/?:/e.t.c' instead of ${condition.text}") {
warnAndFixOnNullCheck(
condition,
isFixable(node, true),
"use '.let/.also/?:/e.t.c' instead of ${condition.text}"
) {
fixNullInIfCondition(node, condition, true)
}
// `!==` and `!==` comparison can be fixed with `.let/also` operators
ElementType.EXCLEQ, ElementType.EXCLEQEQEQ ->
warnAndFixOnNullCheck(condition, true,
"use '.let/.also/?:/e.t.c' instead of ${condition.text}") {
warnAndFixOnNullCheck(
condition,
isFixable(node, false),
"use '.let/.also/?:/e.t.c' instead of ${condition.text}"
) {
fixNullInIfCondition(node, condition, false)
}
else -> return
Expand All @@ -83,6 +92,31 @@ class NullChecksRule(configRules: List<RulesConfig>) : DiktatRule(
}
}

@Suppress("UnsafeCallOnNullableType")
private fun isFixable(condition: ASTNode,
isEqualToNull: Boolean): Boolean {
// Handle cases with `break` word in blocks
val typePair = if (isEqualToNull) {
(ELSE to THEN)
} else {
(THEN to ELSE)
}
val isElseContainBreak = condition
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
.treeParent
.findChildByType(typePair.first)?.let {
it.findChildByType(BLOCK) ?: it
}?.hasChildOfType(BREAK) ?: false
val isThenContainBreakOneLine = condition
.treeParent
.findChildByType(typePair.second)
?.let { it.findChildByType(BLOCK) ?: it }
?.let { astNode ->
astNode.hasChildOfType(BREAK) &&
!emptyBlockList.concat(listOf(BREAK))!!.containsAll(astNode.getChildren(null).map { it.elementType })
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
} ?: false
return !(isElseContainBreak || isThenContainBreakOneLine)
}

@Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION")
private fun fixNullInIfCondition(condition: ASTNode,
binaryExpression: KtBinaryExpression,
Expand All @@ -92,12 +126,27 @@ class NullChecksRule(configRules: List<RulesConfig>) : DiktatRule(
val elseCodeLines = condition.extractLinesFromBlock(ELSE)
val text = if (isEqualToNull) {
when {
elseCodeLines.isNullOrEmpty() -> "$variableName ?: run {\n${thenCodeLines?.joinToString(separator = "\n")}\n}"
elseCodeLines.isNullOrEmpty() ->
if (condition
.treeParent
.findChildByType(THEN)?.let {
it.findChildByType(BLOCK) ?: it
}?.hasChildOfType(BREAK) == true
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
) {
"$variableName ?: break"
} else {
"$variableName ?: run {\n${thenCodeLines?.joinToString(separator = "\n")}\n}"
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
}
thenCodeLines!!.singleOrNull() == "null" -> """
|$variableName?.let {
|${elseCodeLines.joinToString(separator = "\n")}
|}
""".trimMargin()
thenCodeLines.singleOrNull() == "break" -> """
|$variableName?.let {
|${elseCodeLines.joinToString(separator = "\n")}
|} ?: break
""".trimMargin()
else -> """
|$variableName?.let {
|${elseCodeLines.joinToString(separator = "\n")}
Expand All @@ -108,17 +157,19 @@ class NullChecksRule(configRules: List<RulesConfig>) : DiktatRule(
""".trimMargin()
}
} else {
if (elseCodeLines.isNullOrEmpty() || (elseCodeLines.singleOrNull() == "null")) {
"$variableName?.let {\n${thenCodeLines?.joinToString(separator = "\n")}\n}"
} else {
"""
|$variableName?.let {
|${thenCodeLines?.joinToString(separator = "\n")}
|}
|?: run {
|${elseCodeLines.joinToString(separator = "\n")}
|}
""".trimMargin()
when {
elseCodeLines.isNullOrEmpty() || (elseCodeLines.singleOrNull() == "null") ->
"$variableName?.let {\n${thenCodeLines?.joinToString(separator = "\n")}\n}"
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
elseCodeLines.singleOrNull() == "break" ->
"$variableName?.let {\n${thenCodeLines?.joinToString(separator = "\n")}\n} ?: break"
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
else -> """
|$variableName?.let {
|${thenCodeLines?.joinToString(separator = "\n")}
|}
|?: run {
|${elseCodeLines.joinToString(separator = "\n")}
|}
""".trimMargin()
}
}
val tree = KotlinParser().createNode(text)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test

class NullChecksRuleFixTest : FixTestBase("test/paragraph4/null_checks", ::NullChecksRule) {
@Test
@Tag(WarningNames.AVOID_NULL_CHECKS)
fun `should careful fix if conditions with break`() {
fixAndCompare("IfConditionBreakCheckExpected.kt", "IfConditionBreakCheckTest.kt")
}

@Test
@Tag(WarningNames.AVOID_NULL_CHECKS)
fun `should fix if conditions`() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package test.paragraph4.null_checks


fun foo() {
var result: Int? = 19
while(result != 0 ) {
if (result == null) {
foo()
} else {
break
}
}
}

fun foo() {
var result: Int? = 19
while(result != 0 ) {
result?.let {
foo()
} ?: break
}
}

fun foo() {
var result: Int? = 19
while(result != 0 ) {
if (result != null) {
break
} else {
foo()
}
}
}

fun foo() {
var result: Int? = 19
while(result != 0 ) {
result?.let {
foo()
} ?: break
}
}

////////////////////////////////

fun foo() {
var result: Int? = 19
while(result != 0 ) {
result ?: run {
foo()
}
}
}

fun foo() {
var result: Int? = 19
while(result != 0 ) {
result ?: break
}
}

fun foo() {
var result: Int? = 19
while(result != 0 ) {
if (result != null) {
break
}
}
}

fun foo() {
var result: Int? = 19
while(result != 0 ) {
result?.let {
foo()
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package test.paragraph4.null_checks


fun foo() {
var result: Int? = 19
while(result != 0 ) {
if (result == null) {
foo()
} else {
break
}
}
}

fun foo() {
var result: Int? = 19
while(result != 0 ) {
if (result == null) {
break
} else {
foo()
}
}
}

fun foo() {
var result: Int? = 19
while(result != 0 ) {
if (result != null) {
break
} else {
foo()
}
}
}

fun foo() {
var result: Int? = 19
while(result != 0 ) {
if (result != null) {
foo()
} else {
break
}
}
}

////////////////////////////////

fun foo() {
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
var result: Int? = 19
while(result != 0 ) {
if (result == null) {
foo()
}
}
}

fun foo() {
var result: Int? = 19
while(result != 0 ) {
if (result == null) {
break
}
}
}

fun foo() {
var result: Int? = 19
while(result != 0 ) {
if (result != null) {
break
}
}
}

fun foo() {
var result: Int? = 19
while(result != 0 ) {
if (result != null) {
foo()
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,25 @@ value
}
}

fun foo() {
var result: Int? = 10
while (result != 0 ) {
result?.let {
goo()
}
?: run {
for(i in 1..10)
break
}
}
while (result != 0) {
result = goo()
if (result != null) {
goo()
} else {
println(123)
break
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,24 @@ fun test() {
}
}

fun foo() {
var result: Int? = 10
while (result != 0 ) {
if (result != null) {
goo()
} else {
for(i in 1..10)
break
}
}
while (result != 0) {
result = goo()
if (result != null) {
goo()
} else {
println(123)
break
}
}
}