Edit me

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

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).

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 following flags:

    --classpath, -cp
      EXPERIMENTAL: Paths where to find user class files and depending jar
      files. Used for type resolution.
    --jvm-target
      EXPERIMENTAL: Target version of the generated JVM bytecode that was
      generated during compilation and is now being used for type resolution
      (1.6, 1.8, 9, 10, 11 or 12)
      Default: JVM_1_6
      Possible Values: [JVM_1_6, JVM_1_8, JVM_9, JVM_10, JVM_11, JVM_12, JVM_13]

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 Spek for testing, you can use the setupKotlinEnvironment() util function, and get access to the KotlinCoreEnvironment by simply calling val env: KotlinCoreEnvironment by memoized():

class MyRuleSpec : 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), you can use the createEnvironment() method from detekt-test-utils.