Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement requiresTypeResoultion Rule
- Loading branch information
1 parent
0f8cb03
commit f6a5567
Showing
7 changed files
with
201 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
...ruleauthors/src/main/kotlin/io/gitlab/arturbosch/detekt/authors/RequiresTypeResolution.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
@file:Suppress("ForbiddenComment") | ||
|
||
package io.gitlab.arturbosch.detekt.authors | ||
|
||
import io.gitlab.arturbosch.detekt.api.BaseRule | ||
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 io.gitlab.arturbosch.detekt.api.internal.ActiveByDefault | ||
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution | ||
import io.gitlab.arturbosch.detekt.rules.fqNameOrNull | ||
import org.jetbrains.kotlin.psi.KtClass | ||
import org.jetbrains.kotlin.psi.KtElement | ||
import org.jetbrains.kotlin.resolve.BindingContext | ||
import org.jetbrains.kotlin.types.typeUtil.supertypes | ||
import kotlin.reflect.KClass | ||
|
||
/** | ||
* If a rule uses `bindingContext` should be annotated with `@RequiresTypeResolution`. And if it doesn't it shouldn't | ||
* be annotated with it. | ||
*/ | ||
@ActiveByDefault("1.22.0") | ||
@RequiresTypeResolution | ||
class RequiresTypeResolution(config: Config = Config.empty) : Rule(config) { | ||
override val issue = Issue( | ||
javaClass.simpleName, | ||
Severity.Defect, | ||
"`@RequiresTypeResolution` should be used if and only if `bindingContext` is used.", | ||
Debt.FIVE_MINS | ||
) | ||
|
||
override fun visitClass(klass: KtClass) { | ||
super.visitClass(klass) | ||
|
||
if (klass.extendsFrom(BaseRule::class)) { | ||
val usesBindingContext = usesBindingContext(klass) | ||
val isAnnotatedWithRequiresTypeResolution = klass.isAnnotatedWith(RequiresTypeResolution::class) | ||
if (usesBindingContext && !isAnnotatedWithRequiresTypeResolution) { | ||
report( | ||
CodeSmell( | ||
issue, | ||
Entity.atName(klass), | ||
"`${klass.name}` uses `bindingContext` but is not annotated with `@RequiresTypeResolution`" | ||
) | ||
) | ||
} else if (!usesBindingContext && isAnnotatedWithRequiresTypeResolution) { | ||
report( | ||
CodeSmell( | ||
issue, | ||
Entity.atName(klass), | ||
"`${klass.name}` is annotated with `@RequiresTypeResolution` but doesn't use `bindingContext`" | ||
) | ||
) | ||
} | ||
} | ||
} | ||
} | ||
|
||
private fun usesBindingContext(element: KtElement): Boolean { | ||
return element.containingKtFile.text.contains("bindingContext", ignoreCase = false) | ||
} | ||
|
||
context(BaseRule) private inline fun <reified T : Any> KtClass.extendsFrom(kClass: KClass<T>): Boolean { | ||
return bindingContext[BindingContext.CLASS, this] | ||
?.defaultType | ||
?.supertypes() | ||
.orEmpty() | ||
.any { it.fqNameOrNull()?.toString() == checkNotNull(kClass.qualifiedName) } | ||
} | ||
|
||
context(BaseRule) private inline fun <reified T : Any> KtClass.isAnnotatedWith(kClass: KClass<T>): Boolean { | ||
return annotationEntries | ||
.asSequence() | ||
.mapNotNull { it.typeReference } | ||
.mapNotNull { bindingContext[BindingContext.TYPE, it] } | ||
.any { it.fqNameOrNull()?.toString() == checkNotNull(kClass.qualifiedName) } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
detekt-rules-ruleauthors/src/main/resources/config/config.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
detekt: | ||
active: true | ||
RequiresTypeResolution: | ||
active: true |
92 changes: 92 additions & 0 deletions
92
...authors/src/test/kotlin/io/gitlab/arturbosch/detekt/authors/RequiresTypeResolutionSpec.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package io.gitlab.arturbosch.detekt.authors | ||
|
||
import io.gitlab.arturbosch.detekt.rules.KotlinCoreEnvironmentTest | ||
import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment | ||
import org.junit.jupiter.api.Test | ||
|
||
@KotlinCoreEnvironmentTest | ||
internal class RequiresTypeResolutionSpec(private val env: KotlinCoreEnvironment) { | ||
|
||
private val rule = RequiresTypeResolution() | ||
|
||
@Test | ||
fun `should not report classes that doesn't extend from BaseRule`() { | ||
val code = """ | ||
class A { | ||
val issue: Int = error("bindingContext") | ||
} | ||
""" | ||
val findings = rule.compileAndLintWithContext(env, code) | ||
assertThat(findings).isEmpty() | ||
} | ||
|
||
@Test | ||
fun `should report Rules that use bindingContext and are not annotated`() { | ||
val code = """ | ||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.api.Rule | ||
class B(config: Config) : Rule(config) { | ||
override val issue = error("I don't care") | ||
private fun asdf() { | ||
bindingContext | ||
} | ||
} | ||
""" | ||
val findings = rule.compileAndLintWithContext(env, code) | ||
assertThat(findings).hasSize(1) | ||
} | ||
|
||
@Test | ||
fun `should not report Rules that doesn't use bindingContext and are not annotated`() { | ||
val code = """ | ||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.api.Rule | ||
class C(config: Config) : Rule(config) { | ||
override val issue = error("I don't care") | ||
} | ||
""" | ||
val findings = rule.compileAndLintWithContext(env, code) | ||
assertThat(findings).isEmpty() | ||
} | ||
|
||
@Test | ||
fun `should not report Rules that use bindingContext and are annotated`() { | ||
val code = """ | ||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.api.Rule | ||
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution | ||
@RequiresTypeResolution | ||
class D(config: Config) : Rule(config) { | ||
override val issue = error("I don't care") | ||
private fun asdf() { | ||
bindingContext | ||
} | ||
} | ||
""" | ||
val findings = rule.compileAndLintWithContext(env, code) | ||
assertThat(findings).isEmpty() | ||
} | ||
|
||
@Test | ||
fun `should not report Rules that doesn't use bindingContext and are annotated`() { | ||
val code = """ | ||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.api.Rule | ||
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution | ||
@RequiresTypeResolution | ||
class E(config: Config) : Rule(config) { | ||
override val issue = error("I don't care") | ||
} | ||
""" | ||
val findings = rule.compileAndLintWithContext(env, code) | ||
assertThat(findings).hasSize(1) | ||
} | ||
} |