Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement rule to check the correct usage of
@RequiresTypeResolution
(
#5182) Co-authored-by: marschwar <marschwar@users.noreply.github.com> Co-authored-by: Nicola Corti <corti.nico@gmail.com>
- Loading branch information
1 parent
6274f21
commit 3f9e95a
Showing
22 changed files
with
244 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
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
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
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
96 changes: 96 additions & 0 deletions
96
...src/main/kotlin/io/gitlab/arturbosch/detekt/authors/ViolatesTypeResolutionRequirements.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,96 @@ | ||
@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.KtFile | ||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression | ||
import org.jetbrains.kotlin.psi.KtReferenceExpression | ||
import org.jetbrains.kotlin.resolve.BindingContext | ||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull | ||
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny | ||
import kotlin.reflect.KClass | ||
|
||
/** | ||
* If a rule uses the property [BaseRule.bindingContext] should be annotated with `@RequiresTypeResolution`. | ||
* And if the rule doesn't use that property it shouldn't be annotated with it. | ||
*/ | ||
@ActiveByDefault("1.22.0") | ||
@RequiresTypeResolution | ||
class ViolatesTypeResolutionRequirements(config: Config = Config.empty) : Rule(config) { | ||
override val issue = Issue( | ||
javaClass.simpleName, | ||
Severity.Defect, | ||
"`@RequiresTypeResolution` should be used if and only if the property `bindingContext` is used.", | ||
Debt.FIVE_MINS | ||
) | ||
|
||
private val klasses: MutableList<KtClass> = mutableListOf() | ||
private var usesBindingContext: Boolean = false | ||
|
||
override fun visitKtFile(file: KtFile) { | ||
super.visitKtFile(file) | ||
klasses.forEach { 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`" | ||
) | ||
) | ||
} | ||
} | ||
klasses.clear() | ||
usesBindingContext = false | ||
} | ||
|
||
override fun visitClass(klass: KtClass) { | ||
super.visitClass(klass) | ||
|
||
if (klass.extendsFrom(BaseRule::class)) { | ||
klasses.add(klass) | ||
} | ||
} | ||
|
||
override fun visitReferenceExpression(expression: KtReferenceExpression) { | ||
super.visitReferenceExpression(expression) | ||
usesBindingContext = usesBindingContext || | ||
(expression is KtNameReferenceExpression && expression.text == "bindingContext") | ||
} | ||
} | ||
|
||
context(BaseRule) private inline fun <reified T : Any> KtClass.extendsFrom(kClass: KClass<T>): Boolean { | ||
return bindingContext[BindingContext.CLASS, this] | ||
?.getAllSuperclassesWithoutAny() | ||
.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) } | ||
} |
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 @@ | ||
ruleauthors: | ||
active: true | ||
ViolatesTypeResolutionRequirements: | ||
active: true |
115 changes: 115 additions & 0 deletions
115
...test/kotlin/io/gitlab/arturbosch/detekt/authors/ViolatesTypeResolutionRequirementsSpec.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,115 @@ | ||
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 ViolatesTypeResolutionRequirementsSpec(private val env: KotlinCoreEnvironment) { | ||
|
||
private val rule = ViolatesTypeResolutionRequirements() | ||
|
||
@Test | ||
fun `should not report classes that don't extend from BaseRule`() { | ||
val code = """ | ||
class A { | ||
val issue: Int = error("bindingContext") | ||
} | ||
""".trimIndent() | ||
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 A(config: Config) : Rule(config) { | ||
override val issue = error("I don't care") | ||
private fun asdf() { | ||
bindingContext | ||
} | ||
} | ||
""".trimIndent() | ||
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 A(config: Config) : Rule(config) { | ||
override val issue = error("I don't care") | ||
} | ||
""".trimIndent() | ||
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 A(config: Config) : Rule(config) { | ||
override val issue = error("I don't care") | ||
private fun asdf() { | ||
bindingContext | ||
} | ||
} | ||
""".trimIndent() | ||
val findings = rule.compileAndLintWithContext(env, code) | ||
assertThat(findings).isEmpty() | ||
} | ||
|
||
@Test | ||
fun `should report Rules that don'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 A(config: Config) : Rule(config) { | ||
override val issue = error("I don't care") | ||
} | ||
""".trimIndent() | ||
val findings = rule.compileAndLintWithContext(env, code) | ||
assertThat(findings).hasSize(1) | ||
} | ||
|
||
@Test | ||
fun `should report Rules that use bindingContext outside class and are not annotated`() { | ||
val code = """ | ||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.api.Rule | ||
class A(config: Config) : Rule(config) { | ||
override val issue = error("I don't care") | ||
private fun asdf() { | ||
extension() | ||
} | ||
} | ||
inline fun Rule.extension(): Boolean { | ||
bindingContext | ||
return true | ||
} | ||
""".trimIndent() | ||
val findings = rule.compileAndLintWithContext(env, code) | ||
assertThat(findings).hasSize(1) | ||
} | ||
} |
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
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
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