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 2 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 @@ -253,5 +253,33 @@ 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
}
"""
val findings = subject.compileAndLintWithContext(env, code)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one should be just lintWithContext. You doesn't want to compile it because it is meant to no compile.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. Thanks for the hint!


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()
}
}
}
}