-
-
Notifications
You must be signed in to change notification settings - Fork 756
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add UseLet style rule * Use to syntax instead of Pair constructor * Return documentation on UselessCallOnNutNull * Ignore blocks that have more than a null statement * Enable UseLet in detekt.yml * Remove unnecessary return
- Loading branch information
Showing
5 changed files
with
153 additions
and
0 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
63 changes: 63 additions & 0 deletions
63
detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseLet.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,63 @@ | ||
package io.gitlab.arturbosch.detekt.rules.style | ||
|
||
import io.gitlab.arturbosch.detekt.api.CodeSmell | ||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.api.Debt | ||
import io.gitlab.arturbosch.detekt.api.Entity | ||
import io.gitlab.arturbosch.detekt.api.Issue | ||
import io.gitlab.arturbosch.detekt.api.Rule | ||
import io.gitlab.arturbosch.detekt.api.Severity | ||
import io.gitlab.arturbosch.detekt.rules.isNonNullCheck | ||
import io.gitlab.arturbosch.detekt.rules.isNullCheck | ||
import org.jetbrains.kotlin.psi.KtBinaryExpression | ||
import org.jetbrains.kotlin.psi.KtBlockExpression | ||
import org.jetbrains.kotlin.psi.KtConstantExpression | ||
import org.jetbrains.kotlin.psi.KtExpression | ||
import org.jetbrains.kotlin.psi.KtIfExpression | ||
import org.jetbrains.kotlin.psi.KtPsiUtil | ||
|
||
/** | ||
* `if` expressions that either check for not-null and return `null` in the false case or check for `null` and returns | ||
* `null` in the truthy case are better represented as `?.let {}` blocks. | ||
* | ||
* <noncompliant> | ||
* if (x != null) { x.transform() } else null | ||
* if (x == null) null else y | ||
* </noncompliant> | ||
* | ||
* <compliant> | ||
* x?.let { it.transform() } | ||
* x?.let { y } | ||
* </compliant> | ||
*/ | ||
class UseLet( | ||
config: Config = Config.empty | ||
) : Rule(config) { | ||
override val issue = Issue( | ||
javaClass.simpleName, | ||
Severity.Style, | ||
"Use `?.let {}` instead of if/else with a null block when checking for nullable values", | ||
Debt.FIVE_MINS | ||
) | ||
|
||
private fun isExpressionNull(branch: KtExpression?): Boolean { | ||
val statement = when (branch) { | ||
is KtBlockExpression -> if (branch.statements.size == 1) branch.statements.first() else null | ||
is KtConstantExpression -> branch | ||
else -> null | ||
} | ||
|
||
return statement?.let { KtPsiUtil.isNullConstant(it) } ?: false | ||
} | ||
|
||
override fun visitIfExpression(expression: KtIfExpression) { | ||
super.visitIfExpression(expression) | ||
val condition = expression.condition as? KtBinaryExpression ?: return | ||
|
||
if (condition.isNullCheck() && isExpressionNull(expression.then)) { | ||
report(CodeSmell(issue, Entity.from(expression), issue.description)) | ||
} else if (condition.isNonNullCheck() && isExpressionNull(expression.`else`)) { | ||
report(CodeSmell(issue, Entity.from(expression), issue.description)) | ||
} | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseLetSpec.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,85 @@ | ||
package io.gitlab.arturbosch.detekt.rules.style | ||
|
||
import io.gitlab.arturbosch.detekt.test.assertThat | ||
import io.gitlab.arturbosch.detekt.test.compileAndLint | ||
import org.junit.jupiter.api.DynamicTest | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.TestFactory | ||
|
||
class UseLetSpec { | ||
|
||
val subject = UseLet() | ||
|
||
@TestFactory | ||
fun `it forbids all != null else null combinations`(): Iterable<DynamicTest> { | ||
val conditions = listOf( | ||
Triple("1 == null", false, true), | ||
Triple("null == 1", false, true), | ||
Triple("1 == 1", false, false), | ||
Triple("null == null", false, true), | ||
Triple("1 != null", true, false), | ||
Triple("null != 1", true, false), | ||
Triple("1 != 1", false, false), | ||
Triple("null != null", true, false), | ||
) | ||
|
||
val exprs = listOf( | ||
"1" to false, | ||
"null" to true, | ||
"{ 1 }" to false, | ||
"{ null }" to true, | ||
) | ||
|
||
return conditions.flatMap { (condition, isNonNullCheck, isNullCheck) -> | ||
exprs.flatMap { (left, leftIsNull) -> | ||
exprs.map { (right, rightIsNull) -> | ||
DynamicTest.dynamicTest("($condition) $left else $right") { | ||
val expr = "fun test() = if ($condition) $left else $right" | ||
val shouldFail = (isNonNullCheck && rightIsNull) || (isNullCheck && leftIsNull) | ||
val findings = subject.compileAndLint(expr) | ||
if (shouldFail) { | ||
assertThat(findings).hasSize(1) | ||
assertThat(findings[0]).hasMessage(subject.issue.description) | ||
} else { | ||
assertThat(findings).isEmpty() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
fun `do not capture blocks that have multiple expressions`() { | ||
val findings = subject.compileAndLint( | ||
""" | ||
fun testCallToCreateTempFile(s: String?) { | ||
val x = if (s == null) { | ||
println("foo") | ||
null | ||
} else { | ||
"x" | ||
} | ||
} | ||
""".trimIndent() | ||
) | ||
|
||
assertThat(findings).isEmpty() | ||
} | ||
|
||
@Test | ||
fun `it allows the following expressions (currently)`() { | ||
val findings = subject.compileAndLint( | ||
""" | ||
fun testCallToCreateTempFile() { | ||
val x: String? = "abc" | ||
if (x == null) println(x) else null | ||
if (x is String) println(x) | ||
if (x != null) { println(x) } | ||
} | ||
""".trimIndent() | ||
) | ||
|
||
assertThat(findings).isEmpty() | ||
} | ||
} |