Skip to content

Commit

Permalink
Forbid constructors with ForbiddenMethodCall (#5195)
Browse files Browse the repository at this point in the history
* Allow to forbid constructors with ForbiddenMethodCall

* Update detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenMethodCall.kt

Co-authored-by: schalkms <30376729+schalkms@users.noreply.github.com>

* Expand rule description

Co-authored-by: schalkms <30376729+schalkms@users.noreply.github.com>
  • Loading branch information
BraisGabin and schalkms committed Aug 8, 2022
1 parent 5f3ad3e commit 95624c3
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 6 deletions.
Expand Up @@ -30,9 +30,9 @@ import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall

/**
* This rule allows to set a list of forbidden methods. This can be used to discourage the use of unstable, experimental
* or deprecated methods, especially for methods imported from external libraries.
* Detekt will then report all method invocations that are forbidden.
* This rule allows to set a list of forbidden methods or constructors. This can be used to discourage the use
* of unstable, experimental or deprecated methods, especially for methods imported from external libraries.
* Detekt will then report all method or constructor invocations that are forbidden.
*
* <noncompliant>
* import java.lang.System
Expand Down Expand Up @@ -61,7 +61,8 @@ class ForbiddenMethodCall(config: Config = Config.empty) : Rule(config) {
"(i.e. `java.time.LocalDate(java.time.Clock)`) which would report only call " +
"with this concrete signature. If you want to forbid an extension function like" +
"`fun String.hello(a: Int)` you should add the receiver parameter as the first parameter like this: " +
"`hello(kotlin.String, kotlin.Int)`"
"`hello(kotlin.String, kotlin.Int)`. To forbid constructor calls you need to define them with `<init>`, " +
" for example `java.util.Date.<init>`."
)
private val methods: List<Forbidden> by config(
valuesWithReason(
Expand Down
Expand Up @@ -508,4 +508,72 @@ class ForbiddenMethodCallSpec(val env: KotlinCoreEnvironment) {
)
assertThat(findings).hasSize(1)
}

@Nested
inner class `Forbid constructors` {
@Nested
inner class NameOnly {
@Test
fun `empty constructor`() {
val code = """
import java.util.Date
val a = Date()
""".trimIndent()
val findings =
ForbiddenMethodCall(TestConfig(METHODS to listOf("java.util.Date.<init>"))).compileAndLintWithContext(
env,
code
)
assertThat(findings).hasSize(1)
}

@Test
fun `no-empty constructor`() {
val code = """
import java.util.Date
val a = Date(2022, 8 ,7)
""".trimIndent()
val findings =
ForbiddenMethodCall(TestConfig(METHODS to listOf("java.util.Date.<init>"))).compileAndLintWithContext(
env,
code
)
assertThat(findings).hasSize(1)
}
}

@Nested
inner class WithParameters {
@Test
fun `empty constructor`() {
val code = """
import java.util.Date
val a = Date()
""".trimIndent()
val findings =
ForbiddenMethodCall(TestConfig(METHODS to listOf("java.util.Date.<init>()"))).compileAndLintWithContext(
env,
code
)
assertThat(findings).hasSize(1)
}

@Test
fun `no-empty constructor`() {
val code = """
import java.util.Date
val a = Date(2022, 8 ,7)
""".trimIndent()
val findings =
ForbiddenMethodCall(
TestConfig(METHODS to listOf("java.util.Date.<init>(kotlin.Int, kotlin.Int, kotlin.Int)"))
).compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
}
}
}
Expand Up @@ -5,6 +5,7 @@ import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter

Expand All @@ -18,7 +19,7 @@ sealed class FunctionMatcher {
private val fullyQualifiedName: String
) : FunctionMatcher() {
override fun match(callableDescriptor: CallableDescriptor): Boolean {
return callableDescriptor.fqNameOrNull()?.asString() == fullyQualifiedName
return callableDescriptor.fqNameSafe.asString() == fullyQualifiedName
}

override fun match(function: KtNamedFunction, bindingContext: BindingContext): Boolean {
Expand All @@ -36,7 +37,7 @@ sealed class FunctionMatcher {
) : FunctionMatcher() {
override fun match(callableDescriptor: CallableDescriptor): Boolean {
val descriptor = callableDescriptor.original
if (descriptor.fqNameOrNull()?.asString() != fullyQualifiedName) return false
if (descriptor.fqNameSafe.asString() != fullyQualifiedName) return false

val encounteredParamTypes =
(listOfNotNull(descriptor.extensionReceiverParameter) + descriptor.valueParameters)
Expand Down

0 comments on commit 95624c3

Please sign in to comment.