Skip to main content

Using Type Resolution

This page describes how to use detekt's type resolution feature.

caution

Please note that type resolution is still an experimental feature of Detekt. We expect it to be stable with the upcoming release of Detekt (2.x)

What is type resolution

Type resolution is a feature that allows Detekt to perform more advanced static analysis on your Kotlin source code.

Normally, Detekt doesn't have access to the types and symbols that are available to the compiler during the compilation. This restricts the inspection capability. By enabling type resolution, you provide to Detekt all the information to understand types and symbols in your code needed to perform more accurate analysis. This extends Detekt's inspection capability to ones of the Kotlin compiler.

An example

Detekt has a rule called MagicNumber to detect usages of magic numbers in your code.

In the following code:

val user = getUserById(42)?.toString()

Detekt is able to report the usage of the number 42 as a magic number, without type resolution. All the information needed to run this inspection is already available in the source code.

Similarly, Detekt has another rule called UnnecessarySafeCall to detect unnecessary usages of safe call operators (?.).

In the previous example, Detekt is able to determine if the safe call in getUserById(42)?.toString() is required only with type resolution.

This is because Detekt needs to know what is the return type of getUserById() in order to correctly perform the inspection. If the return type is a nullable type, then the code is valid. If the return type is a non-nullable type, Detekt will report an UnnecessarySafeCall as the ?. is actually not needed.

With type resolution, Detekt has access to all the symbols and types of your codebase. Type resolution can be enabled by providing the classpath that is used during compilation. This will give Detekt access to all the code used to compile your project (both first and third party code) and will allow more advanced analysis.

Is my rule using type resolution?

If you're running Detekt without type resolution, all the rules that require type resolution will not run.

All the rules that require type resolution are annotated with @RequiresTypeResolution.

Moreover, their official documentation in the Detekt website will mention Requires Type Resolution (like here).

caution

Please note that we do have some rules that have mixed behavior whether type resolution is enabled or not. Those rules are listed here: #2994

Before opening an issue that you're rule is not working, please verify, whether your rule requires type resolution and check if you have type resolution enabled.

Issues and proposals for rules that require type resolution are labelled with needs type and symbol solving on the Issue tracker.

Enabling on a JVM project

The easiest way to use type resolution is to use the Detekt Gradle plugin. On a JVM project, the following tasks will be created:

  • detekt - Runs detekt WITHOUT type resolution
  • detektMain - Runs detekt with type resolution on the main source set
  • detektTest - Runs detekt with type resolution on the test source set

Moreover, you can use detektBaselineMain and detektBaselineTest to create baselines starting from runs of Detekt with type resolution enabled.

Alternatively, you can create a custom detekt task, making sure to specify the classpath and jvmTarget properties correctly. See the Run detekt using the Detekt Gradle Plugin and the Run detekt using Gradle Task for further readings on this.

Enabling on an Android project

Other than the aforementioned tasks for JVM projects, you can use the following Android-specific gradle tasks:

  • detekt<Variant> - Runs detekt with type resolution on the specific build variant
  • detektBaseline<Variant> - Creates a detekt baselines starting from a run of Detekt with type resolution enabled on the specific build variant.

Alternatively, you can create a custom detekt task, making sure to specify the classpath and jvmTarget properties correctly. Doing this on Android is more complicated due to build types/flavors (see #2259 for further context). Therefore, we recommend using the detekt<Variant> tasks offered by the Gradle plugins.

In case of build related issues, you may try detekt.android.disabled=true in gradle.properties to prevent detekt Gradle plugins from configuring Android-specific gradle tasks.

Enabling on Detekt CLI

If you're using Detekt via CLI, type resolution will be enabled only if you provide the --classpath and --jvm-target flags. See the list of CLI options for details.

Writing a rule that uses type resolution

If you're writing a custom rule or if you're willing to write a rule to contribute to Detekt, you might want to leverage type resolution.

Rules that are using type resolution, access the bindingContext from the BaseRule class (source).

By default, the bindingContext is initialized as BindingContext.EMPTY. This is the default value that the rule receives if type resolution is not enabled.

Therefore, is generally advised to wrap your rules with a check for an empty binding context (source):

    override fun visitCallExpression(expression: KtCallExpression) {
super.visitCallExpression(expression)

if (bindingContext == BindingContext.EMPTY) return

// Rest of the rule that will run only with type resolution enabled.
}

If the bindingContext is not EMPTY, you are free to use it to resolve types and get access to all the information needed for your rules. As a rule of thumb, we recommend to get inspiration from other rules on how they're using the bindingContext.

Testing a rule that uses type resolution

To test a rule that uses type resolution, you can use the lintWithContext and compileAndLintWithContext extension functions.

If you're using JUnit 5 for testing, you can use the @KotlinCoreEnvironmentTest annotation on your test class, and accept a parameter of type KotlinCoreEnvironment in the class constructor. You can then access the environment by referencing the parameter specified in the constructor:

@KotlinCoreEnvironmentTest
class MyRuleSpec(private val env: KotlinCoreEnvironment) {
@Test
fun `reports cast that cannot succeed`() {
val code = """/* The code you want to test */"""
assertThat(MyRuleSpec().compileAndLintWithContext(env, code)).hasSize(1)
}
}

If you're using Spek for testing, you can create a setupKotlinEnvironment() util function, and get access to the KotlinCoreEnvironment by simply calling val env: KotlinCoreEnvironment by memoized():

fun org.spekframework.spek2.dsl.Root.setupKotlinEnvironment(additionalJavaSourceRootPath: Path? = null) {
val wrapper by memoized(
CachingMode.SCOPE,
{ createEnvironment(additionalJavaSourceRootPaths = listOfNotNull(additionalJavaSourceRootPath?.toFile())) },
{ it.dispose() }
)

// `env` name is used for delegation
@Suppress("UNUSED_VARIABLE")
val env: KotlinCoreEnvironment by memoized(CachingMode.EACH_GROUP) { wrapper.env }
}

class MyRuleTest : Spek({
setupKotlinEnvironment()

val env: KotlinCoreEnvironment by memoized()

it("reports cast that cannot succeed") {
val code = """/* The code you want to test */"""
assertThat(MyRuleSpec().compileAndLintWithContext(env, code)).hasSize(1)
}
})

If you're using another testing framework (e.g. JUnit 4), you can use the createEnvironment() method from detekt-test-utils.