Skip to content

Commit

Permalink
Create RequiresTypeResolutionRulesDoesNotRunWithoutAContext
Browse files Browse the repository at this point in the history
  • Loading branch information
BraisGabin committed Jul 25, 2022
1 parent 7da777b commit babab25
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 2 deletions.
Expand Up @@ -13,6 +13,10 @@ class AuthorsProvider : RuleSetProvider {

override val ruleSetId: String = "detekt"

@Suppress("UseEmptyCounterpart")
override fun instance(config: Config) = RuleSet(ruleSetId, listOf())
override fun instance(config: Config) = RuleSet(
ruleSetId,
listOf(
RequiresTypeResolutionRulesDoesNotRunWithoutAContext(config),
)
)
}
@@ -0,0 +1,77 @@
package io.gitlab.arturbosch.detekt.authors

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 org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.findFunctionByName

/**
* A rule annotated with RequiresTypeResolution should override `visitCondition` and return false if the provided
* `bindingContext` is empty.
*
* <noncompliant>
* @RequiresTypeResolution
* class MyRule(config: Config = Config.empty) : Rule(config) {
* override fun visitKtFile(file: KtFile) =
* if (bindingContext == BindingContext.EMPTY) return
* ...
* }
* }
* </noncompliant>
*
* <compliant>
* @RequiresTypeResolution
* class MyRule(config: Config = Config.empty) : Rule(config) {
* override fun visitCondition(root: KtFile) =
* bindingContext != BindingContext.EMPTY && super.visitCondition(root)
* }
* }
* </compliant>
*/
@ActiveByDefault("1.22.0")
class RequiresTypeResolutionRulesDoesNotRunWithoutAContext(config: Config = Config.empty) : Rule(config) {
override val issue = Issue(
javaClass.simpleName,
Severity.Style,
"Rules marked as RequiresTypeResolution shouldn't run without `bindingContext`.",
Debt.FIVE_MINS
)

override fun visitClass(klass: KtClass) {
if (klass.annotationEntries.map { it.text }.contains("@RequiresTypeResolution")) {
val function: KtNamedFunction? = klass.findFunctionByName("visitCondition") as? KtNamedFunction

if (function == null) {
report(
CodeSmell(
issue,
Entity.atName(klass),
message
)
)
return
}

if (function.bodyExpression?.text != requiredImplementation) {
report(
CodeSmell(
issue,
Entity.atName(function),
message
)
)
}
}
}
}

private const val requiredImplementation = "bindingContext != BindingContext.EMPTY && super.visitCondition(root)"
private const val message = "This rule is annotated with `RequiresTypeResolution` but `visitCondition` " +
"implementation is not `$requiredImplementation`"
2 changes: 2 additions & 0 deletions detekt-authors/src/main/resources/config/config.yml
@@ -1,2 +1,4 @@
detekt:
active: true
RequiresTypeResolutionRulesDoesNotRunWithoutAContext:
active: true
@@ -0,0 +1,80 @@
package io.gitlab.arturbosch.detekt.authors

import io.gitlab.arturbosch.detekt.test.assertThat
import io.gitlab.arturbosch.detekt.test.compileAndLint
import org.junit.jupiter.api.Test

class RequiresTypeResolutionRulesDoesNotRunWithoutAContextSpec {

private val rule = RequiresTypeResolutionRulesDoesNotRunWithoutAContext()

@Test
fun `should not report no annotated classes`() {
val code = """
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Rule
class A(config: Config = Config.empty) : Rule(config) {
override val issue = error("I don't care")
}
"""
val findings = rule.compileAndLint(code)
assertThat(findings).isEmpty()
}

@Test
fun `should report annotated classes without visitCondition()`() {
val code = """
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Rule
@RequiresTypeResolution
class A(config: Config = Config.empty) : Rule(config) {
override val issue = error("I don't care")
}
"""
val findings = rule.compileAndLint(code)
assertThat(findings).hasSize(1)
}

@Test
fun `should report annotated classes with visitCondition() but no check for bindingContext`() {
val code = """
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Rule
import org.jetbrains.kotlin.psi.KtFile
@RequiresTypeResolution
class A(config: Config = Config.empty) : Rule(config) {
override val issue = error("I don't care")
override fun visitCondition(root: KtFile) = super.visitCondition(root)
}
"""
val findings = rule.compileAndLint(code)
assertThat(findings).hasSize(1)
}

@Test
fun `should not report annotated classes with visitCondition() and check for bindingContext first line`() {
val code = """
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Rule
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
@RequiresTypeResolution
class A(config: Config = Config.empty) : Rule(config) {
override val issue = error("I don't care")
override fun visitCondition(root: KtFile) =
bindingContext != BindingContext.EMPTY && super.visitCondition(root)
}
"""
val findings = rule.compileAndLint(code)
assertThat(findings).isEmpty()
}
}

0 comments on commit babab25

Please sign in to comment.