detekt: Cannot write extension that requires a bindingContext

Moving over the very helpful discussion that I incorrectly started against the PR to add ForbiddenMethodCall check to styles. I am trying to write a custom extension that does a very similar thing and yet my visitor is never called with non-empty bindingContext. I took @cortinico’s very helpful advice and made some additions to my configuration but that does not seem to help. To debug, I attempted to simply use the ForbiddenMethodCall check but could not even get that to work. I am going to paste (part of) our configuration below for additional context. Any help you can offer would be great! Thank you for all the help and such a great product!

Expected Behavior

The use of a forbidden method should be flagged.

Observed Behavior

None of the methods were flagged as being banned.

Steps to Reproduce

I am running detekt with gradle via: ./gradlew detekt

Context

Your Environment

  • Version of detekt used: 1.3.1
  • Version of Gradle used (if applicable): 5.6.0
  • Operating System and version: Linux
  • Link to your project (if it’s a public repository): This branch is not yet public. Relevant config:
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath Deps.tools_androidgradle
        classpath Deps.tools_kotlingradle
        classpath Deps.androidx_safeargs
        classpath Deps.allopen
        classpath Deps.osslicenses_plugin

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

plugins {
    id("io.gitlab.arturbosch.detekt").version("1.3.1")
}

allprojects {
    repositories {
        google()
        maven {
            url "https://snapshots.maven.mozilla.org/maven2"
        }
        maven {
            url "https://maven.mozilla.org/maven2"
        }
        maven {
            url "https://repo.leanplum.com/"
        }
        jcenter()
    }
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
        kotlinOptions.jvmTarget = "1.8"
        kotlinOptions.allWarningsAsErrors = true
        kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental", "-Xskip-runtime-version-check"]
    }


}

task clean(type: Delete) {
    delete rootProject.buildDir
}

detekt {
    // The version number is duplicated, please refer to plugins block for more details
    version = "1.3.1"
    input = files("$projectDir/app/src")
    config = files("$projectDir/config/detekt.yml")
    //exclude = ".*test.*,.*/resources/.*,.*/tmp/.*"

    reports {
        html {
            enabled = true
            destination = file("$projectDir/build/reports/detekt.html")
        }
        xml {
            enabled = false
        }
    }
}

configurations {
    ktlint
}

dependencies {
    ktlint "com.pinterest:ktlint:0.34.2"
    detekt project(":mozilla-detekt-rules")
    detekt "io.gitlab.arturbosch.detekt:detekt-cli:${Versions.detekt}"
}

task ktlint(type: JavaExec, group: "verification") {
    description = "Check Kotlin code style."
    classpath = configurations.ktlint
    main = "com.pinterest.ktlint.Main"
    args "app/src/**/*.kt"
}

tasks.withType(io.gitlab.arturbosch.detekt.Detekt) {
    jvmTarget = "1.8"
}

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 17 (7 by maintainers)

Most upvoted comments

I think that this is about the best that I am going to do. As a recap, here is my goal and my context:

Context: An Android application written in Kotlin with a large number of dependencies.

Goal: Use a custom Detekt plugin that determines whether banned methods are called. The custom plugin requires a binding context in order to do method resolution. Without resolution, writing such a plugin is possible, but would have to be imprecise. For example, consider a scenario where we want to ban Foo.methodA. Resolution is necessary to determine whether either invoking x.methodA() or y.methodA() are okay. We need to know the type of x and the type of y. In the absence of resolution information, we would have to resort to banning all calls to methodA which would lead to a significant number of false positives which would, in turn, lead developers to develop mistrust of the tool.

Solution: To get a binding context in the custom plugin from Detekt, it is necessary to configure Detekt with a classpath and a jvmTarget. That is relatively straightforward:

detekt {
   ...
   classpath = <some classpath>
   jvmTarget = "1.8"
}

The problem is, with Android’s finagling in gradle, a meaningful classpath that takes into account the Android application’s dependencies is not completely built until after the configuration phase of the gradle lifecycle is complete. This is after a SourceTask or VerificationTask executes (the type of the Detekt task). Therefore, we have to delay execution of the Detekt task until after the configuration phase of the gradle lifecycle is complete.

Doing this is not easy, but it is possible. Here’s how we achieved it:

task detektWithAndroidContext() {

    setGroup("verification")
    setDescription("Run Detekt with a symbol-resolving classpath in order to perform more precise checking.")

    // Only execute these behaviors when the configuration phase of the gradle build lifecycle is complete.
    doLast {
        // Create a new Detekt task.
        def t = this.project.tasks.create("testing", io.gitlab.arturbosch.detekt.Detekt)
        // Configure that task according to our site specifications
        t.configure {
            def projectDir = this.project.getProjectDir().toString()

            // Collect a classpath that uses each of the available variants. This classpath includes all the Android
            // project's dependencies. This is only available after the configuration phase of the gradle build
            // lifecycle is complete.
            def fs = files()
            android.applicationVariants.each {
                // Check each element of the classpath to see if it exists before adding it to the list.
                fs += it.javaCompile.classpath.filter { it.exists() }
            }
            // Set a classpath that includes the Android-specific classpath that we just built.
            classpath.setFrom(fs)

            // Replicate standard detekt configuration closure items.

            // Put the equivalent to the 'input =' value from the detekt configuration closure here.
            setSource(files("$projectDir/src/"))
            // This include is the default for Detekt. It must be set explicitly because it does not
            // get set here automatically like it does when the 'input =' line is set in the
            // detekt configuration closure.
            include("**/*.kt", "**/*.kts")
            config.setFrom(files("$projectDir/config/detekt.yml"))
            jvmTarget = "1.8"
            reports {
                html {
                    enabled = true
                    destination = file("$projectDir/build/reports/detekt.html")
                }
                xml {
                    enabled = false
                }
            }
        }
        // Finally, call check on the Detekt task in order to execute.
        t.check()
    }
}

I will eventually turn this into a blog post, but I want to get it down on paper early in case it helps someone else!

Still working through some issues on this getting the classpath exactly right. I will post as soon as everything is perfect. Sorry for the delay and thanks again everyone!

I was able to get this to work. I will post here later tonight with my solution for people who are looking at it in the future and might be having the same problem! Thank you to @arturbosch and @schalkms and others who helped debug this!

It looks like https://arturbosch.github.io/detekt/groovydsl.html still refers to the old ways of configuring?

This really helps, thank you! I guess that I know have the same question about setting the other items that would normally go in the detekt “closure” like config, excludes etc?

obviously, yes!! If you think that it would be useful. I wasn’t sure people would find it so.