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

Forbid constructors with ForbiddenMethodCall #5195

Merged
merged 3 commits into from Aug 8, 2022
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 @@ -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