Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions detekt-core/src/main/resources/default-detekt-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,8 @@ potential-bugs:
active: false
MissingWhenCase:
active: true
NullableToStringCall:
active: false
RedundantElseInWhen:
active: true
UnconditionalJumpStatementInLoop:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package io.gitlab.arturbosch.detekt.rules.bugs

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.descriptors.CallableDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.KtStringTemplateEntry
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.jetbrains.kotlin.types.isNullable

/**
* Turn on this rule to flag 'toString' calls with a nullable receiver that may return the string "null".
*
* <noncompliant>
* fun foo(a: Any?): String {
* return a.toString()
* }
*
* fun bar(a: Any?): String {
* return "$a"
* }
* </noncompliant>
*
* <compliant>
* fun foo(a: Any?): String {
* return a?.toString() ?: "-"
* }
*
* fun bar(a: Any?): String {
* return "${a ?: "-"}"
* }
* </compliant>
*
* @since 1.11.0
*/
class NullableToStringCall(config: Config = Config.empty) : Rule(config) {
override val issue = Issue(
javaClass.simpleName,
Severity.Defect,
"This call may return the string \"null\"",
Debt.FIVE_MINS
)

@Suppress("ReturnCount")
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
super.visitSimpleNameExpression(expression)

if (bindingContext == BindingContext.EMPTY) return

val qualified = expression.getQualifiedExpressionForReceiver()
when {
qualified != null -> {
if (qualified.descriptor()?.fqNameOrNull() != FqName("kotlin.toString")) return
}
expression.parent is KtStringTemplateEntry -> {
val compilerResources = compilerResources ?: return
val descriptor = expression.descriptor() ?: return
val originalType = descriptor.returnType ?.takeIf { it.isNullable() } ?: return
val dataFlowInfo =
bindingContext[BindingContext.EXPRESSION_TYPE_INFO, expression]?.dataFlowInfo ?: return
val dataFlowValue = compilerResources.dataFlowValueFactory.createDataFlowValue(
expression, originalType, bindingContext, descriptor
)
val dataFlowTypes =
dataFlowInfo.getStableTypes(dataFlowValue, compilerResources.languageVersionSettings)
if (dataFlowTypes.any { !it.isNullable() }) return
}
else -> return
}

val targetExpression = qualified ?: expression.parent
val codeSmell = CodeSmell(
issue,
Entity.from(targetExpression),
"This call '${targetExpression.text}' may return the string \"null\"."
)
report(codeSmell)
}

private fun KtExpression.descriptor(): CallableDescriptor? = getResolvedCall(bindingContext)?.resultingDescriptor
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class PotentialBugProvider : DefaultRuleSetProvider {
UselessPostfixExpression(config),
WrongEqualsTypeParameter(config),
IgnoredReturnValue(config),
ImplicitUnitReturnType(config)
ImplicitUnitReturnType(config),
NullableToStringCall(config)
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.rules.setupKotlinEnvironment
import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe

object NullableToStringCallSpec: Spek({
setupKotlinEnvironment()

val env: KotlinCoreEnvironment by memoized()
val subject by memoized { NullableToStringCall() }

describe("NullableToString rule") {
it("reports when a nullable toString is explicitly called") {
val code = """
fun test(a: Any?) {
println(a.toString())
}
"""
val actual = subject.compileAndLintWithContext(env, code)
assertThat(actual).hasSize(1)
assertThat(actual.first().message).isEqualTo("This call 'a.toString()' may return the string \"null\".")
}

it("reports when a nullable toString is implicitly called in a string template") {
val code = """
fun test(a: Any?) {
println("${'$'}a")
}
"""
val actual = subject.compileAndLintWithContext(env, code)
assertThat(actual).hasSize(1)
assertThat(actual.first().message).isEqualTo("This call '\$a' may return the string \"null\".")
}

it("reports when a nullable toString is implicitly called in curly braces in a string template") {
val code = """
fun test(a: Any?) {
println("${'$'}{a}")
}
"""
val actual = subject.compileAndLintWithContext(env, code)
assertThat(actual).hasSize(1)
assertThat(actual.first().message).isEqualTo("This call '\${a}' may return the string \"null\".")
}

it("reports when a nullable toString is implicitly called in a raw string template") {
val code = """
fun test(a: Any?) {
println(${'"'}""${'$'}a""${'"'})
}
"""
val actual = subject.compileAndLintWithContext(env, code)
assertThat(actual).hasSize(1)
assertThat(actual.first().message).isEqualTo("This call '\$a' may return the string \"null\".")
}

it("does not report when a nullable toString is not called") {
val code = """
fun test(a: Any?) {
println(a?.toString())
println("${'$'}{a ?: "-"}")
}
fun test2(a: Any) {
println(a.toString())
println("${'$'}a")
println("${'$'}{a}")
println(${'"'}""${'$'}a""${'"'})
}
fun test3(a: Any?) {
if (a != null) {
println(a.toString())
println("${'$'}a")
println("${'$'}{a}")
println(${'"'}""${'$'}a""${'"'})
}
}
fun test4(a: Any?) {
requireNotNull(a)
println(a.toString())
println("${'$'}a")
println("${'$'}{a}")
println(${'"'}""${'$'}a""${'"'})
}
"""
val actual = subject.compileAndLintWithContext(env, code)
assertThat(actual).isEmpty()
}
}
})
32 changes: 32 additions & 0 deletions docs/pages/documentation/potential-bugs.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,38 @@ fun whenOnEnumCompliant2(c: Color) {
}
```

### NullableToStringCall

Turn on this rule to flag 'toString' calls with a nullable receiver that may return the string "null".

**Severity**: Defect

**Debt**: 5min

#### Noncompliant Code:

```kotlin
fun foo(a: Any?): String {
return a.toString()
}

fun bar(a: Any?): String {
return "$a"
}
```

#### Compliant Code:

```kotlin
fun foo(a: Any?): String {
return a?.toString() ?: "-"
}

fun bar(a: Any?): String {
return "${a ?: "-"}"
}
```

### RedundantElseInWhen

Turn on this rule to flag `when` expressions that contain a redundant `else` case. This occurs when it can be
Expand Down