diff --git a/build.gradle.kts b/build.gradle.kts index fcfddd54..d912478a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,9 +59,7 @@ dependencies { "ktlint-reporter-json", "ktlint-reporter-html", "ktlint-reporter-plain", - "ktlint-reporter-sarif", - "ktlint-ruleset-experimental", - "ktlint-ruleset-standard" + "ktlint-reporter-sarif" ).forEach { module -> compileOnly("com.pinterest.ktlint:$module:${Versions.ktlint}") testImplementation("com.pinterest.ktlint:$module:${Versions.ktlint}") diff --git a/src/main/kotlin/org/jmailen/gradle/kotlinter/support/RuleSets.kt b/src/main/kotlin/org/jmailen/gradle/kotlinter/support/RuleSets.kt index 8308860e..0c2e42ce 100644 --- a/src/main/kotlin/org/jmailen/gradle/kotlinter/support/RuleSets.kt +++ b/src/main/kotlin/org/jmailen/gradle/kotlinter/support/RuleSets.kt @@ -2,15 +2,13 @@ package org.jmailen.gradle.kotlinter.support import com.pinterest.ktlint.core.RuleProvider import com.pinterest.ktlint.core.RuleSetProviderV2 -import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider import java.util.ServiceLoader internal fun resolveRuleProviders( - providers: Iterable, includeExperimentalRules: Boolean = false, -): Set = providers +): Set = defaultRuleSetProviders() .asSequence() - .filter { includeExperimentalRules || it !is ExperimentalRuleSetProvider } + .filter { includeExperimentalRules || it.id != "experimental" } .sortedWith( compareBy { when (it.id) { @@ -23,7 +21,9 @@ internal fun resolveRuleProviders( .flatten() .toSet() -// statically resolve providers from plugin classpath. ServiceLoader#load alone resolves classes lazily which fails when run in parallel -// https://github.com/jeremymailen/kotlinter-gradle/issues/101 -val defaultRuleSetProviders: List = +/** + * Make sure this gets called with proper classpath (i.e. within Gradle Worker class) + * `toList()` call prevents concurrency issues: https://github.com/jeremymailen/kotlinter-gradle/issues/101 + */ +private fun defaultRuleSetProviders(): List = ServiceLoader.load(RuleSetProviderV2::class.java).toList() diff --git a/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/format/FormatWorkerAction.kt b/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/format/FormatWorkerAction.kt index 41f7bf6f..db6a1b9b 100644 --- a/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/format/FormatWorkerAction.kt +++ b/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/format/FormatWorkerAction.kt @@ -3,14 +3,12 @@ package org.jmailen.gradle.kotlinter.tasks.format import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.LintError import com.pinterest.ktlint.core.RuleProvider -import org.gradle.api.logging.LogLevel import org.gradle.api.logging.Logger 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.KtLintParams -import org.jmailen.gradle.kotlinter.support.defaultRuleSetProviders import org.jmailen.gradle.kotlinter.support.editorConfigOverride import org.jmailen.gradle.kotlinter.support.resetEditorconfigCacheIfNeeded import org.jmailen.gradle.kotlinter.support.resolveRuleProviders @@ -30,21 +28,22 @@ abstract class FormatWorkerAction : WorkAction { changedEditorconfigFiles = parameters.changedEditorConfigFiles, logger = logger, ) + val ruleSets = resolveRuleProviders(includeExperimentalRules = ktLintParams.experimentalRules) + logger.info("Resolved ${ruleSets.size} RuleSetProviders") val fixes = mutableListOf() try { files.forEach { file -> - val ruleSets = resolveRuleProviders(defaultRuleSetProviders, ktLintParams.experimentalRules) val sourceText = file.readText() val relativePath = file.toRelativeString(projectDirectory) - logger.log(LogLevel.DEBUG, "$name checking format: $relativePath") + logger.debug("$name checking format: $relativePath") when (file.extension) { "kt" -> this::formatKt "kts" -> this::formatKts else -> { - logger.log(LogLevel.DEBUG, "$name ignoring non Kotlin file: $relativePath") + logger.debug("$name ignoring non Kotlin file: $relativePath") null } }?.let { formatFunc -> @@ -53,11 +52,11 @@ abstract class FormatWorkerAction : WorkAction { true -> "${file.path}:${error.line}:${error.col}: Format fixed > [${error.ruleId}] ${error.detail}" false -> "${file.path}:${error.line}:${error.col}: Format could not fix > [${error.ruleId}] ${error.detail}" } - logger.log(LogLevel.QUIET, msg) + logger.quiet(msg) fixes.add(msg) } if (!formattedText.contentEquals(sourceText)) { - logger.log(LogLevel.QUIET, "${file.path}: Format fixed") + logger.quiet("${file.path}: Format fixed") file.writeText(formattedText) } } diff --git a/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/lint/LintWorkerAction.kt b/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/lint/LintWorkerAction.kt index 1f01743d..9ba7f18f 100644 --- a/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/lint/LintWorkerAction.kt +++ b/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/lint/LintWorkerAction.kt @@ -10,7 +10,6 @@ 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.KtLintParams -import org.jmailen.gradle.kotlinter.support.defaultRuleSetProviders import org.jmailen.gradle.kotlinter.support.editorConfigOverride import org.jmailen.gradle.kotlinter.support.reporterFor import org.jmailen.gradle.kotlinter.support.reporterPathFor @@ -34,12 +33,17 @@ abstract class LintWorkerAction : WorkAction { changedEditorconfigFiles = parameters.changedEditorconfigFiles, logger = logger, ) + val ruleSets = resolveRuleProviders(includeExperimentalRules = ktLintParams.experimentalRules) + logger.info("Resolved ${ruleSets.size} RuleSetProviders") + if (logger.isDebugEnabled) { + logger.debug("Resolved RuleSetProviders = ${ruleSets.joinToString { it.createNewRuleInstance().id }}") + } + var hasError = false try { reporters.onEach { it.beforeAll() } files.forEach { file -> - val ruleSets = resolveRuleProviders(defaultRuleSetProviders, ktLintParams.experimentalRules) val relativePath = file.toRelativeString(projectDirectory) reporters.onEach { it.before(relativePath) } logger.debug("$name linting: $relativePath") diff --git a/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/ExtensionTest.kt b/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/ExtensionTest.kt index daac4757..c65cf384 100644 --- a/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/ExtensionTest.kt +++ b/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/ExtensionTest.kt @@ -136,4 +136,43 @@ internal class ExtensionTest : WithGradleTest.Kotlin() { assertEquals(TaskOutcome.SUCCESS, task(":lintKotlinMain")?.outcome) } } + + @Test + fun `can override ktlint version`() { + projectRoot.resolve("build.gradle") { + // language=groovy + val buildScript = + """ + plugins { + id 'kotlin' + id 'org.jmailen.kotlinter' + } + + repositories { + mavenCentral() + } + + kotlinter { + ktlintVersion = "0.46.0" + } + + """.trimIndent() + writeText(buildScript) + } + projectRoot.resolve("src/main/kotlin/FileName.kt") { + writeText(kotlinClass("FileName")) + } + + buildAndFail("lintKotlin").apply { + assertEquals(TaskOutcome.FAILED, task(":lintKotlinMain")?.outcome) { "should fail due to incompatibility" } + val expectedMessage = "Caused by: java.lang.NoSuchMethodError: 'void com.pinterest.ktlint.core.KtLint\$ExperimentalParams." + assertTrue(output.contains(expectedMessage)) { "should explain the incompatibility" } + } + // remove `--configuration-cache-problems=warn` when upgrading to Gradle 7.6 https://github.com/gradle/gradle/issues/17470 + build("dependencies", "--configuration", "ktlint", "--configuration-cache-problems=warn").apply { + assertTrue(output.contains("com.pinterest:ktlint:0.46.0")) { + "should include overridden ktlin version in `ktlint` configuration" + } + } + } } diff --git a/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/KotlinProjectTest.kt b/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/KotlinProjectTest.kt index f1d0deff..73988e79 100644 --- a/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/KotlinProjectTest.kt +++ b/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/KotlinProjectTest.kt @@ -186,6 +186,30 @@ internal class KotlinProjectTest : WithGradleTest.Kotlin() { } } + @Test + fun `plugin resolves dynamically loaded RulesetProviders`() { + settingsFile() + buildFile() + editorConfig() + kotlinSourceFile( + "CustomObject.kt", + """ + object CustomObject + + """.trimIndent(), + ) + + build("lintKotlin", "--info").apply { + assertEquals(SUCCESS, task(":lintKotlin")?.outcome) + assertTrue(output.contains("Resolved 45 RuleSetProviders")) + } + + build("formatKotlin", "--info").apply { + assertEquals(SUCCESS, task(":formatKotlin")?.outcome) + assertTrue(output.contains("Resolved 45 RuleSetProviders")) + } + } + private fun settingsFile() = settingsFile.apply { writeText("rootProject.name = 'kotlinter'") } diff --git a/src/test/kotlin/org/jmailen/gradle/kotlinter/support/RuleSetsTest.kt b/src/test/kotlin/org/jmailen/gradle/kotlinter/support/RuleSetsTest.kt deleted file mode 100644 index 033989d3..00000000 --- a/src/test/kotlin/org/jmailen/gradle/kotlinter/support/RuleSetsTest.kt +++ /dev/null @@ -1,70 +0,0 @@ -package org.jmailen.gradle.kotlinter.support - -import com.pinterest.ktlint.core.KtLint -import com.pinterest.ktlint.core.Rule -import com.pinterest.ktlint.core.RuleProvider -import com.pinterest.ktlint.core.RuleSetProviderV2 -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test - -class RuleSetsTest { - - @Test - fun `resolveRuleSets loads from classpath providers`() { - val standardOnly = resolveRuleProviders(defaultRuleSetProviders, includeExperimentalRules = false) - val withExperimentalRules = resolveRuleProviders(defaultRuleSetProviders, includeExperimentalRules = true) - - assertTrue(standardOnly.isNotEmpty()) - assertTrue(standardOnly.size < withExperimentalRules.size) - } - - @Test - fun `resolveRuleSets puts standard rules first`() { - val standard = TestRuleSetProvider("standard", setOf(TestRule("one"))) - val extra1 = TestRuleSetProvider("extra-one", setOf(TestRule("two"))) - val extra2 = TestRuleSetProvider("extra-two", setOf(TestRule("three"))) - - val result = resolveRuleProviders(providers = listOf(extra2, standard, extra1)).map { it.createNewRuleInstance() } - - assertEquals(3, result.size) - assertEquals(standard.ruleSet.single(), result.first()) - assertTrue(result.containsAll(listOf(extra1.ruleSet.single(), extra2.ruleSet.single()))) - } - - @Test - fun `test compatibility`() { - KtLint.lint( - KtLint.ExperimentalParams( - fileName = "/tmp/src/test/KotlinClass.kt", - text = """ - package test - - class KotlinClass { - private fun hi() { - println("hi") - } - } - - """.trimIndent(), - ruleProviders = resolveRuleProviders(defaultRuleSetProviders), - cb = { _, _ -> }, - ), - ) - } -} - -class TestRuleSetProvider(id: String, val ruleSet: Set) : RuleSetProviderV2( - id = id, - about = About( - maintainer = "stub-maintainer", - description = "stub-description", - license = "stub-license", - repositoryUrl = "stub-repositoryUrl", - issueTrackerUrl = "stub-issueTrackerUrl", - ), -) { - override fun getRuleProviders() = ruleSet.map { rule -> RuleProvider(provider = { rule }) }.toSet() -} - -class TestRule(id: String) : Rule(id)