Detekt Dangers and custom reports

In my previous post on setting up detekt and danger/kotlin I mentioned an awesome AckeeCZ/danger-kotlin-detekt danger plugin to parse and report detekt work results.

Though it has been doing its job great for us (big thanks to the folks) I felt we need more capabilities to customize what and how we want to report based on parsed results. I'm happy to introduce you pavelkorolevxyz/danger-detekt-kotlin plugin. It's heavily inspired on Ackee's plugin, but I left more space to play here.

How it looks like

Depends on you. We constrained only with danger/kotlin file and line appearance, and repository hosting (e.g. GitHub) markdown capabilities. Anything else is customizable.

Like this

Comment

With inline comments

Inline

Or this

No warnings

Usage

I'll assume you are familiar with danger-kotlin setting up already. If not you could find more information in official documentation or my previous post.

Dangerfile.df.kts is the main configuration file of any danger/kotlin setup. To use this plugin you should add it as a dependency on top of this file and call register.

@file:DependsOn("xyz.pavelkorolev.danger.detekt:plugin:x.y.z")

register.plugin(DetektPlugin)

Plugin itself is a fat jar published to mavenCentral, so you don't need anything more to do.

Basic

Basically that's what Ackee's plugin had as its only option.

Single file parse and report

This does what it says. If you have one detekt report and don't want any customization - that's probably your choice.

DetektPlugin.parseAndReport(reportFile)

Multiple files parse and report

Actually parameters of all parse functions are varargs, so you could provide it as many report files as you want.

DetektPlugin.parseAndReport(reportFile1, reportFile2, reportFile3)

or

val files: Array<File> = findReportFilesByYourself()
DetektPlugin.parseAndReport(*files)

Let's see parse and report on their own.

Parse

That's something new. You could also parse files without immediate reporting.

val report: DetektReport = DetektPlugin.parse(files)

This DetektReport contains everything from parsed detekt reports, so it could be useful you want to check something before actual reporting.

Report

You could also report it like this

DetektPlugin.report(report)

Please note, in order to make danger report files correctly you should configure detekt to return paths relative to working directory.

detekt {
    basePath = rootDir.absolutePath
    // Other configuration
}

Full example

@file:DependsOn("xyz.pavelkorolev.danger.detekt:plugin:x.y.z")

import systems.danger.kotlin.*
import systems.danger.kotlin.models.github.*
import xyz.pavelkorolev.danger.detekt.DetektPlugin
import java.io.File

register.plugin(DetektPlugin)

danger(args) {
    warnDetekt()
}

fun warnDetekt() {
    val file = File("build/reports/detekt/report.xml")
    if (!file.exists()) {
        warn(
            "🙈 No detekt report found",
        )
        return
    }
    with(DetektPlugin) {
        val report = parse(file)
        val count = report.count
        if (count == 0) {
            message("👏👏👏 Good job! Detekt found no violations here!")
            return
        }
        fail(
            "🙁 Detekt violations found: **${report.count}**.\n" +
                    "Please fix them to proceed. We have zero-warning policy"
        )
        report(report)
    }
}

Similar Dangerfile could be found on the main branch of pavelkorolevxyz/detekt-danger-sample-android repository I used in previous post to demonstrate full danger-kotlin + detekt setup.

Customization

Functions DetektPlugin.report and DetektPlugin.parseAndReport have reporter: DetektErrorReporter parameter, which is in fact a functional interface with report(error: DetektError, fileName: String?) function.

By implementing this you could customize reporting logic and appearance as you want.

By default, there is DefaultDetektErrorReporter which has its own opinionated way to create messages and respect violations severities.

DefaultDetektErrorReporter uses inline comments if file and line provided for error. If you want to use only global report without inline comments then use DefaultDetektErrorReporter(context, isInlineEnabled = false) instead.

Implementation

Let's say you want to send everything found into fail table with emojis at the end. Write it like this.

class FailReporter(private val context: DangerContext): DetektErrorReporter {

    override fun report(error: DetektError, fileName: String?) {
        val message = error.message ?: return
        context.fail("$message 💥💥💥")
    }
}

And use it like this

DetektPlugin.report(report, reporter = FailReporter(context))

Or you could implement the same as inline reporter thanks to its functional interface nature like so.

plugin.report(report) { error, _ ->  
    error.message?.let(context::fail)  
}

I think you got the idea.

Ideas

I'll try to give you some ideas of how to use this plugin customization capabilities.

Conclusion

That's it. Feel free to contact me on Github repo or any way you want. I'm looking forward for you to share your ideas or use cases.