Improving Detekt's Quality Reporting with the Problems API
Static analysis tools like Detekt and Ktlint are popular and invaluable tools that are used for maintaining high code quality in Kotlin projects. An issue that affects a lot of developers is that the reporting of issues found in a project often speak different languages in a sense. You may see warnings in one format, errors in another, and diagnostics somewhere else. This results in developers juggling multiple outputs, CI jobs having to parse different report styles, and IDEs may struggle to present a cohesive view of issues.
We can unify these outputs via the Gradle Problems API. The Problems API reports rich structured information about problems happening during the build. This information can be used by different user interfaces such as Gradle’s console output, Build Scans, or IDEs to communicate problems to the user in the most appropriate way.
In my Google Summer of Code 2025 project, Enhanced Kotlin Code Quality Reporting with the Gradle Problems API, I built a proof of concept integration into Detekt.
Implementing a ProblemsApiConsoleReport
There are a few different types of reports that could have been considered which are the OutputReport and the ConsoleReport. However, since the Problems API surfaces structured problems and we don’t want to duplicate a formatted report, ConsoleReport is the appropriate tool for translating Detekt issues to the Problems API reporter.
Key behaviors of the custom report:
- Injects Gradle’s
Problems
service. - Iterates over
Detektion.issues
, creating aProblemId
for each. - Populates metadata (file location, line number, severity, details) on the
ProblemSpec
. - Collects a fallback textual summary for output
detektion.issues.forEach { issue ->
val group = ProblemGroup.create("validation", "detekt issue")
val id = ProblemId.create(issue.ruleInstance.id, issue.message, group)
reporter?.report(id) { spec ->
val filePath = issue.location.path.toString()
val line = issue.location.source.line
spec.fileLocation(filePath)
spec.lineInFileLocation(filePath, line)
spec.details(issue.message)
spec.severity(mapSeverity(issue.severity))
reportLines.add(
"${issue.location.path}:${issue.location.source.line} [${issue.ruleInstance.id}] ${issue.message}"
)
}
}
return reportLines.joinToString(separator = System.lineSeparator())
}
}
After publishing locally:
./gradlew publishToMavenLocal
Testing
I built the plugin jar and injected it into a temporary project via DslGradleRunner with this command:
./gradlew :detekt-report-problems-api:jar
The jar file was used in the dependencies of the mainBuildFileContent of the DslGradleRunner in the functional test:
mainBuildFileContent = """
plugins {
id("io.gitlab.arturbosch.detekt")
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
detektPlugins(files("${pluginJar.replace('\\', '/')}"))
detektPlugins(gradleApi())
}
detekt {
allRules = true
ignoreFailures = false
}
""".trimIndent(),
dryRun = false
).also {
it.setupProject()
val badClass = it.projectFile("src/main/kotlin/BadClass.kt")
badClass.parentFile.mkdirs()
badClass.writeText("class badClassName")
}
The test sets up a deliberately bad Kotlin source class badClassName and expects Detekt to fail:
gradleRunner.runTasksAndExpectFailure("detekt") { result ->
val output = result.output
assertThat(output).contains("The file does not contain a package declaration.")
}
To validate the Problems API wiring more directly several tests were written, for example this unit test mocks Detektion and captures the ProblemSpec to verify that the correct fields (details, severity, file/line) are populated:
whenever(detektion.issues).thenReturn(listOf(issue))
val result = report.render(detektion)
assertThat(result).isEqualTo("")
val specCaptor = argumentCaptor<Action<ProblemSpec>>()
verify(problemReporter).report(any(), specCaptor.capture())
val spec: ProblemSpec = mock()
specCaptor.firstValue.execute(spec)
verify(spec).details("Class name should match the pattern...")
verify(spec).severity(GradleSeverity.ERROR)
verify(spec).lineInFileLocation(any(), eq(4))
verify(spec).fileLocation(any())
Performance
Problems API Branch Performance Build Scan
Main Branch Build Scan
The Problems API integration does not seem to drastically change overall performance.
If you want to stay connected about the Problems API, join the Problems API channel in the gradle community slack!
You can also see the project page here which is still in progress.
Upcoming
The Problems API will be integrated in Ktlint and will be in a future post!
- Vanessa Johnson (GSoC 2025 Contributor)
Top comments (0)