-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add rule to ban extension functions on nullable receivers (#26)
- Loading branch information
1 parent
c860491
commit 8ec1632
Showing
5 changed files
with
120 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
58 changes: 58 additions & 0 deletions
58
src/main/kotlin/com/faire/detekt/rules/NoExtensionFunctionOnNullableReceiver.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,58 @@ | ||
package com.faire.detekt.rules | ||
|
||
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 org.jetbrains.kotlin.psi.KtNamedFunction | ||
import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration | ||
|
||
/** | ||
* This rule reports extension functions on nullable types. | ||
* Exceptions are made for functions that return non-nullable types since they do not introduce nullability. | ||
* | ||
* The reason for this rule is that extension functions on nullable types can obscure the nullability of the receiver. | ||
* | ||
* ``` | ||
* // Bad | ||
* fun String?.foo(): String? = this | ||
* | ||
* nonNullString.foo() // Return value is nullable which needs to be handled downstream | ||
* | ||
* // Good | ||
* fun String.foo(): String = this | ||
* | ||
* nonNullString.foo() // Return value stays non-null | ||
* nullableString?.foo() // Use null-safe operator to handle nullable receiver | ||
* | ||
* // Okay — function returns non-nullable type, essentially converting a nullable into a non-nullable type | ||
* fun String?.emptyIfNull(): String = this ?: "" | ||
* ``` | ||
*/ | ||
internal class NoExtensionFunctionOnNullableReceiver(config: Config = Config.empty) : Rule(config) { | ||
override val issue: Issue = Issue( | ||
id = javaClass.simpleName, | ||
severity = Severity.Warning, | ||
description = "This rule reports extension functions on nullable types.", | ||
debt = Debt.FIVE_MINS, | ||
) | ||
|
||
override fun visitNamedFunction(function: KtNamedFunction) { | ||
super.visitNamedFunction(function) | ||
|
||
if (!function.isExtensionDeclaration()) return | ||
if (function.receiverTypeReference?.text?.endsWith("?") != true) return | ||
if (function.typeReference?.text?.endsWith("?") != true) return | ||
|
||
report( | ||
CodeSmell( | ||
issue = issue, | ||
entity = Entity.from(function), | ||
message = "No extension functions on nullable types", | ||
), | ||
) | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
src/test/kotlin/com/faire/detekt/rules/NoExtensionFunctionOnNullableReceiverTest.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,56 @@ | ||
package com.faire.detekt.rules | ||
|
||
import io.gitlab.arturbosch.detekt.test.assertThat | ||
import io.gitlab.arturbosch.detekt.test.compileAndLint | ||
import org.junit.jupiter.api.BeforeEach | ||
import org.junit.jupiter.api.Test | ||
|
||
internal class NoExtensionFunctionOnNullableReceiverTest { | ||
private lateinit var rule: NoExtensionFunctionOnNullableReceiver | ||
|
||
@BeforeEach | ||
fun setup() { | ||
rule = NoExtensionFunctionOnNullableReceiver() | ||
} | ||
|
||
@Test | ||
fun `flag non-private extension function on nullable type`() { | ||
val findings = rule.compileAndLint( | ||
""" | ||
fun String?.foo(): String? = this | ||
""".trimIndent(), | ||
) | ||
assertThat(findings).hasSize(1) | ||
} | ||
|
||
@Test | ||
fun `do not flag extension function on non-nullable type`() { | ||
val findings = rule.compileAndLint( | ||
""" | ||
fun String.foo(): String? = this | ||
""".trimIndent(), | ||
) | ||
assertThat(findings).isEmpty() | ||
} | ||
|
||
@Test | ||
fun `do not flag extension function that returns non-null type`() { | ||
val findings = rule.compileAndLint( | ||
""" | ||
fun String?.foo(): String = this ?: "" | ||
""".trimIndent(), | ||
) | ||
assertThat(findings).isEmpty() | ||
} | ||
|
||
@Test | ||
fun `do not flag if manually suppressed`() { | ||
val findings = rule.compileAndLint( | ||
""" | ||
@Suppress("NoExtensionFunctionOnNullableReceiver") | ||
fun String?.foo(): String? = this | ||
""".trimIndent(), | ||
) | ||
assertThat(findings).isEmpty() | ||
} | ||
} |