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

NonBooleanPropertyPrefixedWithIs: Allow boolean function reference #4684

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -10,11 +10,14 @@ import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution
import io.gitlab.arturbosch.detekt.rules.fqNameOrNull
import io.gitlab.arturbosch.detekt.rules.identifierName
import org.jetbrains.kotlin.builtins.isFunctionOrKFunctionTypeWithAnySuspendability
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.typeBinding.createTypeBindingForReturnType
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isBoolean

/**
* Reports when property with 'is' prefix doesn't have a boolean type.
Expand Down Expand Up @@ -63,9 +66,14 @@ class NonBooleanPropertyPrefixedWithIs(config: Config = Config.empty) : Rule(con
val name = declaration.identifierName()

if (name.startsWith("is") && name.length > 2 && !name[2].isLowerCase()) {
val typeName = getTypeName(declaration)
val type = getType(declaration)
val typeName = type?.getTypeName()
val isNotBooleanType = typeName != kotlinBooleanTypeName && typeName != javaBooleanTypeName

if (typeName != null && typeName != kotlinBooleanTypeName && typeName != javaBooleanTypeName) {
if (!typeName.isNullOrEmpty() &&
isNotBooleanType &&
!type.isBooleanFunctionReference()
) {
report(
reportCodeSmell(declaration, name, typeName)
)
Expand All @@ -85,10 +93,17 @@ class NonBooleanPropertyPrefixedWithIs(config: Config = Config.empty) : Rule(con
)
}

private fun getTypeName(parameter: KtCallableDeclaration): String? {
return parameter.createTypeBindingForReturnType(bindingContext)
private fun getType(parameter: KtCallableDeclaration): KotlinType? =
parameter.createTypeBindingForReturnType(bindingContext)
?.type
?.fqNameOrNull()

private fun KotlinType.getTypeName(): String? =
fqNameOrNull()
?.asString()

private fun KotlinType.isBooleanFunctionReference(): Boolean {
if (!isFunctionOrKFunctionTypeWithAnySuspendability) return false

return arguments.count() == 1 && arguments[0].type.isBoolean()
}
}
Expand Up @@ -3,6 +3,7 @@ package io.gitlab.arturbosch.detekt.rules.naming
import io.gitlab.arturbosch.detekt.rules.KotlinCoreEnvironmentTest
import io.gitlab.arturbosch.detekt.test.assertThat
import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext
import io.gitlab.arturbosch.detekt.test.lintWithContext
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
Expand Down Expand Up @@ -253,5 +254,35 @@ class NonBooleanPropertyWithPrefixIsSpec(val env: KotlinCoreEnvironment) {
assertThat(findings).hasSize(1)
}
}

@Nested
inner class `issue regression test` {
@Test
fun `issue 4674 should handle unknown type as correct`() {
val code = """
class Test {
val isDebuggable get() = BuildConfig.DEBUG
}
"""

// BuildConfig is missing in this test so we can't compile it
val findings = subject.lintWithContext(env, code)

assertThat(findings).isEmpty()
}

@Test
fun `issue 4675 check function reference type parameter`() {
val code = """
val isRemoved = suspend { null == null }

fun trueFun() = true
val isReferenceBoolean = ::trueFun
"""
val findings = subject.compileAndLintWithContext(env, code)

assertThat(findings).isEmpty()
}
}
}
}