Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional configuration for formatKotlin task to fail build if it cannot fix all lint errors #349

Merged
merged 14 commits into from
Nov 14, 2023
Merged
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ Options are configured in the `kotlinter` extension. Defaults shown (you may omi

```kotlin
kotlinter {
failBuildWhenCannotAutoFormat = false
ignoreFailures = false
reporters = arrayOf("checkstyle", "plain")
}
Expand All @@ -167,13 +168,16 @@ kotlinter {

```groovy
kotlinter {
failBuildWhenCannotAutoFormat = false
ignoreFailures = false
reporters = ['checkstyle', 'plain']
}
```

</details>

Setting `failBuildWhenCannotAutoFormat` to `true` will configure the `formatKotlin` task to fail the build when auto-format is not able to fix a lint error. This is overrided by setting `ignoreFailures` to `true`.

Options for `reporters`: `checkstyle`, `html`, `json`, `plain`, `sarif`

Reporters behave as described at: https://github.com/pinterest/ktlint
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ dependencies {

testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${Versions.junit}")
testImplementation("org.junit.jupiter:junit-jupiter-api:${Versions.junit}")
testImplementation("org.junit.jupiter:junit-jupiter-params:${Versions.junit}")
testImplementation("commons-io:commons-io:2.12.0")
testImplementation("org.mockito.kotlin:mockito-kotlin:${Versions.mockitoKotlin}")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import org.jmailen.gradle.kotlinter.support.ReporterType

open class KotlinterExtension {
companion object {
const val DEFAULT_FAIL_BUILD_WHEN_CANNOT_AUTO_FORMAT = false
const val DEFAULT_IGNORE_FAILURES = false
val DEFAULT_REPORTER = ReporterType.checkstyle.name
}

var failBuildWhenCannotAutoFormat = DEFAULT_FAIL_BUILD_WHEN_CANNOT_AUTO_FORMAT
var ignoreFailures = DEFAULT_IGNORE_FAILURES

var reporters = arrayOf(DEFAULT_REPORTER)
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class KotlinterPlugin : Plugin<Project> {
FormatTask::class.java,
) { formatTask ->
formatTask.source(resolvedSources)
formatTask.failBuildWhenCannotAutoFormat.set(provider { kotlinterExtension.failBuildWhenCannotAutoFormat })
formatTask.ignoreFailures.set(provider { kotlinterExtension.ignoreFailures })
formatTask.report.set(reportFile("$id-format.txt"))
}
formatKotlin.configure { formatTask ->
Expand Down
19 changes: 19 additions & 0 deletions src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/FormatTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import org.gradle.api.GradleException
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.work.InputChanges
import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
import org.jmailen.gradle.kotlinter.KotlinterExtension
import org.jmailen.gradle.kotlinter.support.KotlinterError
import org.jmailen.gradle.kotlinter.support.LintFailure
import org.jmailen.gradle.kotlinter.tasks.format.FormatWorkerAction
import javax.inject.Inject

Expand All @@ -27,6 +31,14 @@ open class FormatTask @Inject constructor(
@Optional
val report: RegularFileProperty = objectFactory.fileProperty()

@Input
val ignoreFailures: Property<Boolean> = objectFactory.property(default = KotlinterExtension.DEFAULT_IGNORE_FAILURES)

@Input
val failBuildWhenCannotAutoFormat: Property<Boolean> = objectFactory.property(
default = KotlinterExtension.DEFAULT_FAIL_BUILD_WHEN_CANNOT_AUTO_FORMAT,
)

init {
outputs.upToDateWhen { false }
}
Expand All @@ -48,5 +60,12 @@ open class FormatTask @Inject constructor(
forEach { logger.error(it.message, it.cause) }
throw GradleException("error formatting sources for $name")
}

if (failBuildWhenCannotAutoFormat.get()) {
val lintFailures = result.exceptionOrNull()?.workErrorCauses<LintFailure>() ?: emptyList()
if (lintFailures.isNotEmpty() && !ignoreFailures.get()) {
throw GradleException("$name sources failed lint check")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.gradle.api.logging.Logging
import org.gradle.internal.logging.slf4j.DefaultContextAwareTaskLogger
import org.gradle.workers.WorkAction
import org.jmailen.gradle.kotlinter.support.KotlinterError
import org.jmailen.gradle.kotlinter.support.LintFailure
import org.jmailen.gradle.kotlinter.support.ktlintEngine
import org.jmailen.gradle.kotlinter.support.resetEditorconfigCacheIfNeeded
import org.jmailen.gradle.kotlinter.tasks.FormatTask
Expand All @@ -26,6 +27,7 @@ abstract class FormatWorkerAction : WorkAction<FormatWorkerParameters> {
)
val fixes = mutableListOf<String>()

var hasError = false
try {
files.forEach { file ->

Expand All @@ -44,7 +46,12 @@ abstract class FormatWorkerAction : WorkAction<FormatWorkerParameters> {
true -> "${file.path}:${error.line}:${error.col}: Format fixed > [${error.ruleId.value}] ${error.detail}"
false -> "${file.path}:${error.line}:${error.col}: Format could not fix > [${error.ruleId.value}] ${error.detail}"
}
logger.warn(msg)
if (corrected) {
logger.warn(msg)
} else {
hasError = true
logger.error(msg)
}
fixes.add(msg)
}
if (!formattedText.contentEquals(sourceText)) {
Expand All @@ -56,6 +63,10 @@ abstract class FormatWorkerAction : WorkAction<FormatWorkerParameters> {
throw KotlinterError("format worker execution error", t)
}

if (hasError) {
throw LintFailure("kotlin source failed lint check")
}

output?.writeText(
when (fixes.isEmpty()) {
true -> "ok"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,65 @@
package org.jmailen.gradle.kotlinter.functional

import org.gradle.testkit.runner.TaskOutcome
import org.jmailen.gradle.kotlinter.functional.utils.KotlinterConfig
import org.jmailen.gradle.kotlinter.functional.utils.editorConfig
import org.jmailen.gradle.kotlinter.functional.utils.kotlinClass
import org.jmailen.gradle.kotlinter.functional.utils.resolve
import org.jmailen.gradle.kotlinter.functional.utils.settingsFile
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.io.File

internal class EditorConfigTest : WithGradleTest.Kotlin() {

lateinit var projectRoot: File

@BeforeEach
fun setUp() {
private fun setup(kotlinterConfig: KotlinterConfig) {
projectRoot = testProjectDir.apply {
resolve("settings.gradle") { writeText(settingsFile) }
resolve("build.gradle") {
// language=groovy
val buildScript =
"""
plugins {
id 'kotlin'
id 'org.jmailen.kotlinter'
val buildscript = when (kotlinterConfig) {
KotlinterConfig.DEFAULT ->
"""
plugins {
id 'kotlin'
id 'org.jmailen.kotlinter'
}
""".trimIndent()
KotlinterConfig.FAIL_BUILD_WHEN_CANNOT_AUTO_FORMAT ->
"""
plugins {
id 'org.jetbrains.kotlin.js'
id 'org.jmailen.kotlinter'
}

kotlinter {
failBuildWhenCannotAutoFormat = true
}
""".trimIndent()
KotlinterConfig.IGNORE_FAILURES ->
"""
plugins {
id 'org.jetbrains.kotlin.js'
id 'org.jmailen.kotlinter'
}

kotlinter {
ignoreFailures = true
failBuildWhenCannotAutoFormat = true
}
""".trimIndent()
}

""".trimIndent()
writeText(buildScript)
writeText(buildscript)
}
}
}

@Test
fun `lintTask uses default indentation if editorconfig absent`() {
setup(KotlinterConfig.DEFAULT)
projectRoot.resolve("src/main/kotlin/FourSpacesByDefault.kt") {
writeText(
""" |package com.example
Expand All @@ -56,6 +79,7 @@ internal class EditorConfigTest : WithGradleTest.Kotlin() {

@Test
fun `plugin respects disabled_rules set in editorconfig`() {
setup(KotlinterConfig.DEFAULT)
projectRoot.resolve(".editorconfig") {
appendText(
// language=editorconfig
Expand All @@ -76,6 +100,7 @@ internal class EditorConfigTest : WithGradleTest.Kotlin() {

@Test
fun `plugin respects 'indent_size' set in editorconfig`() {
setup(KotlinterConfig.DEFAULT)
projectRoot.resolve(".editorconfig") {
appendText(
// language=editorconfig
Expand Down Expand Up @@ -107,6 +132,7 @@ internal class EditorConfigTest : WithGradleTest.Kotlin() {

@Test
fun `editorconfig changes are taken into account on lint task re-runs`() {
setup(KotlinterConfig.DEFAULT)
projectRoot.resolve(".editorconfig") {
writeText(
// language=editorconfig
Expand Down Expand Up @@ -148,6 +174,7 @@ internal class EditorConfigTest : WithGradleTest.Kotlin() {

@Test
fun `editorconfig changes are ignored for format task re-runs`() {
setup(KotlinterConfig.DEFAULT)
projectRoot.resolve(".editorconfig") {
writeText(editorConfig)
}
Expand Down Expand Up @@ -177,4 +204,37 @@ internal class EditorConfigTest : WithGradleTest.Kotlin() {
assertFalse(output.contains("resetting KtLint caches"))
}
}

@Test
fun `editorconfig changes are ignored for format task re-runs when failBuildWhenCannotAutoFormat enabled`() {
setup(KotlinterConfig.FAIL_BUILD_WHEN_CANNOT_AUTO_FORMAT)
projectRoot.resolve(".editorconfig") {
writeText(editorConfig)
}

projectRoot.resolve("src/main/kotlin/FileName.kt") {
writeText(kotlinClass("DifferentClassName"))
}
buildAndFail("formatKotlin").apply {
assertEquals(TaskOutcome.FAILED, task(":formatKotlinMain")?.outcome)
assertTrue(
output.contains("Format could not fix > [standard:filename] File 'FileName.kt' contains a single top level declaration"),
)
}

projectRoot.resolve(".editorconfig") {
writeText(
// language=editorconfig
"""
[*.{kt,kts}]
ktlint_standard_filename = disabled
""".trimIndent(),
)
}
buildAndFail("formatKotlin", "--info").apply {
assertEquals(TaskOutcome.FAILED, task(":formatKotlinMain")?.outcome)
assertTrue(output.contains("Format could not fix"))
assertFalse(output.contains("resetting KtLint caches"))
}
}
}
Loading