DEV Community

Cover image for Enhanced Kotlin Code Quality Reporting with the Gradle Problems API
Vanessa Johnson for Gradle Community

Posted on • Edited on

Enhanced Kotlin Code Quality Reporting with the Gradle Problems API

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 a ProblemId 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())
   }
}
Enter fullscreen mode Exit fullscreen mode

After publishing locally:

./gradlew publishToMavenLocal
Enter fullscreen mode Exit fullscreen mode

Testing

I built the plugin jar and injected it into a temporary project via DslGradleRunner with this command:

./gradlew :detekt-report-problems-api:jar 
Enter fullscreen mode Exit fullscreen mode

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")
   }
Enter fullscreen mode Exit fullscreen mode

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.")
   }
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

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)