Repository: jeremymailen/kotlinter-gradle
Pull request #304 (2022-12-16 15:21:35)

Bump ktlint to 0.48.1
I'm not actually sure this works, I just wondered how complex the update would be

jeremymailen: Very much appreciate you working up the changes for the 0.48.0 update. I'll try to find some time over the next week or two to test and review. Thanks in advance for your patience -- some holiday travel coming up and work is extraordinarily hectic as well.

I've also noticed that ktlint intends to do a patch release soon to fix some regressions so we probably will want to jump to that version.
jeremymailen: I'm back from break and have some time to help get this one through. Thanks again!

Patch:
diff --git a/build.gradle.kts b/build.gradle.kts
index f158cf6..3e7caf3 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -27,7 +27,7 @@ description = projectDescription
 object Versions {
     const val androidTools = "7.3.1"
     const val junit = "5.9.1"
-    const val ktlint = "0.47.1"
+    const val ktlint = "0.48.1"
     const val mockitoKotlin = "4.1.0"
 }
 
diff --git a/src/main/kotlin/org/jmailen/gradle/kotlinter/support/EditorConfigUtils.kt b/src/main/kotlin/org/jmailen/gradle/kotlinter/support/EditorConfigUtils.kt
index a415f1c..cbeba01 100644
--- a/src/main/kotlin/org/jmailen/gradle/kotlinter/support/EditorConfigUtils.kt
+++ b/src/main/kotlin/org/jmailen/gradle/kotlinter/support/EditorConfigUtils.kt
@@ -1,33 +1,83 @@
 package org.jmailen.gradle.kotlinter.support
 
-import com.pinterest.ktlint.core.KtLint
-import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties
+import com.pinterest.ktlint.core.api.EditorConfigDefaults
 import com.pinterest.ktlint.core.api.EditorConfigOverride
-import org.gradle.api.file.ConfigurableFileCollection
+import com.pinterest.ktlint.core.api.editorconfig.EditorConfigProperty
+import org.ec4j.core.model.EditorConfig
+import org.ec4j.core.model.Glob
+import org.ec4j.core.model.Property
+import org.ec4j.core.model.PropertyType
+import org.ec4j.core.model.PropertyType.PropertyValueParser.IDENTITY_VALUE_PARSER
+import org.ec4j.core.model.Section
 import org.gradle.api.file.ProjectLayout
-import org.gradle.api.logging.Logger
 import java.io.File
 
-internal fun editorConfigOverride(ktLintParams: KtLintParams): EditorConfigOverride {
-    val rules = ktLintParams.disabledRules
+internal fun editorConfigOverride(ktLintParams: KtLintParams) =
+    getPropertiesForDisabledRules(ktLintParams)
+        .let(::buildEditorConfigOverride)
+
+internal fun editorConfigDefaults(ktLintParams: KtLintParams): EditorConfigDefaults =
+    getPropertiesForExperimentalRules(ktLintParams)
+        .let(::buildEditorConfigDefaults)
 
+private fun getPropertiesForDisabledRules(
+    ktLintParams: KtLintParams,
+): List<Pair<EditorConfigProperty<String>, String>> {
+    val rules = ktLintParams.disabledRules
     return if (rules.isEmpty()) {
-        EditorConfigOverride.emptyEditorConfigOverride
+        emptyList()
     } else {
-        EditorConfigOverride.from(DefaultEditorConfigProperties.ktlintDisabledRulesProperty to rules.joinToString(separator = ","))
+        rules
+            .asSequence()
+            .map(::getKtlintRulePropertyName)
+            .map { propertyName ->
+                EditorConfigProperty(
+                    type = PropertyType(propertyName, "Rule to be disabled", IDENTITY_VALUE_PARSER),
+                    defaultValue = "disabled",
+                )
+            }
+            .map { it to "disabled" }
+            .toList()
     }
 }
 
-internal fun resetEditorconfigCacheIfNeeded(
-    changedEditorconfigFiles: ConfigurableFileCollection,
-    logger: Logger,
-) {
-    val changedFiles = changedEditorconfigFiles.files
-    if (changedFiles.any()) {
-        logger.info("Editorconfig changed, resetting KtLint caches")
-        changedFiles.map(File::toPath).forEach(KtLint::reloadEditorConfigFile)
+private fun getPropertiesForExperimentalRules(ktLintParams: KtLintParams) =
+    Property
+        .builder()
+        .name("ktlint_experimental")
+        .value(if (ktLintParams.experimentalRules) "enabled" else "disabled")
+        .let(::listOf)
+
+private fun buildEditorConfigOverride(editorConfigProperties: List<Pair<EditorConfigProperty<String>, String>>) =
+    if (editorConfigProperties.isEmpty()) {
+        EditorConfigOverride.EMPTY_EDITOR_CONFIG_OVERRIDE
+    } else {
+        EditorConfigOverride.from(*editorConfigProperties.toTypedArray())
+    }
+
+private fun buildEditorConfigDefaults(kotlinSectionProperties: List<Property.Builder>) =
+    if (kotlinSectionProperties.isEmpty()) {
+        EditorConfigDefaults.EMPTY_EDITOR_CONFIG_DEFAULTS
+    } else {
+        EditorConfigDefaults(
+            EditorConfig
+                .builder()
+                .section(
+                    Section
+                        .builder()
+                        .glob(Glob("*.{kt,kts}"))
+                        .properties(kotlinSectionProperties),
+                )
+                .build(),
+        )
+    }
+
+private fun getKtlintRulePropertyName(ruleName: String) =
+    if (ruleName.contains(':')) { // Rule from a non-standard rule set
+        "ktlint_${ruleName.replace(':', '_')}"
+    } else {
+        "ktlint_standard_$ruleName"
     }
-}
 
 internal fun ProjectLayout.findApplicableEditorConfigFiles(): Sequence<File> {
     val projectEditorConfig = projectDirectory.file(".editorconfig").asFile
diff --git a/src/main/kotlin/org/jmailen/gradle/kotlinter/support/KtlintEngineUtils.kt b/src/main/kotlin/org/jmailen/gradle/kotlinter/support/KtlintEngineUtils.kt
new file mode 100644
index 0000000..463d20b
--- /dev/null
+++ b/src/main/kotlin/org/jmailen/gradle/kotlinter/support/KtlintEngineUtils.kt
@@ -0,0 +1,23 @@
+package org.jmailen.gradle.kotlinter.support
+
+import com.pinterest.ktlint.core.KtLintRuleEngine
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.logging.Logger
+import java.io.File
+
+internal fun createKtlintEngine(ktLintParams: KtLintParams) = KtLintRuleEngine(
+    ruleProviders = resolveRuleProviders(defaultRuleSetProviders),
+    editorConfigOverride = editorConfigOverride(ktLintParams),
+    editorConfigDefaults = editorConfigDefaults(ktLintParams),
+)
+
+internal fun KtLintRuleEngine.resetEditorconfigCacheIfNeeded(
+    changedEditorconfigFiles: ConfigurableFileCollection,
+    logger: Logger,
+) {
+    val changedFiles = changedEditorconfigFiles.files
+    if (changedFiles.any()) {
+        logger.info("Editorconfig changed, resetting KtLint caches")
+        changedFiles.map(File::toPath).forEach(::reloadEditorConfigFile)
+    }
+}
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 8308860..7b3a156 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,12 @@ 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<RuleSetProviderV2>,
-    includeExperimentalRules: Boolean = false,
 ): Set<RuleProvider> = providers
     .asSequence()
-    .filter { includeExperimentalRules || it !is ExperimentalRuleSetProvider }
     .sortedWith(
         compareBy {
             when (it.id) {
diff --git a/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/LintTask.kt b/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/LintTask.kt
index ba03305..f294144 100644
--- a/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/LintTask.kt
+++ b/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/LintTask.kt
@@ -52,7 +52,7 @@ open class LintTask @Inject constructor(
                 p.projectDirectory.set(projectLayout.projectDirectory.asFile)
                 p.reporters.putAll(reports)
                 p.ktLintParams.set(getKtLintParams())
-                p.changedEditorconfigFiles.from(getChangedEditorconfigFiles(inputChanges))
+                p.changedEditorConfigFiles.from(getChangedEditorconfigFiles(inputChanges))
             }
             runCatching { await() }
         }
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 5b411fa..53ef90f 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
@@ -1,8 +1,6 @@
 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 com.pinterest.ktlint.core.Code
 import org.gradle.api.logging.LogLevel
 import org.gradle.api.logging.Logger
 import org.gradle.api.logging.Logging
@@ -10,10 +8,8 @@ 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.createKtlintEngine
 import org.jmailen.gradle.kotlinter.support.resetEditorconfigCacheIfNeeded
-import org.jmailen.gradle.kotlinter.support.resolveRuleProviders
 import org.jmailen.gradle.kotlinter.tasks.FormatTask
 import java.io.File
 
@@ -26,7 +22,8 @@ abstract class FormatWorkerAction : WorkAction<FormatWorkerParameters> {
     private val output: File? = parameters.output.asFile.orNull
 
     override fun execute() {
-        resetEditorconfigCacheIfNeeded(
+        val ktLintEngine = createKtlintEngine(ktLintParams = ktLintParams)
+        ktLintEngine.resetEditorconfigCacheIfNeeded(
             changedEditorconfigFiles = parameters.changedEditorConfigFiles,
             logger = logger,
         )
@@ -34,32 +31,27 @@ abstract class FormatWorkerAction : WorkAction<FormatWorkerParameters> {
         val fixes = mutableListOf<String>()
         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")
 
-                when (file.extension) {
-                    "kt" -> this::formatKt
-                    "kts" -> this::formatKts
-                    else -> {
-                        logger.log(LogLevel.DEBUG, "$name ignoring non Kotlin file: $relativePath")
-                        null
-                    }
-                }?.let { formatFunc ->
-                    val formattedText = formatFunc.invoke(file, ruleSets) { error, corrected ->
-                        val msg = when (corrected) {
-                            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)
-                        fixes.add(msg)
-                    }
-                    if (!formattedText.contentEquals(sourceText)) {
-                        logger.log(LogLevel.QUIET, "${file.path}: Format fixed")
-                        file.writeText(formattedText)
+                if (file.extension !in supportedExtensions) {
+                    logger.log(LogLevel.DEBUG, "$name ignoring non Kotlin file: $relativePath")
+                    return@forEach
+                }
+
+                val formattedText = ktLintEngine.format(Code.CodeFile(file)) { error, corrected ->
+                    val msg = when (corrected) {
+                        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)
+                    fixes.add(msg)
+                }
+                if (!formattedText.contentEquals(sourceText)) {
+                    logger.log(LogLevel.QUIET, "${file.path}: Format fixed")
+                    file.writeText(formattedText)
                 }
             }
         } catch (t: Throwable) {
@@ -73,27 +65,6 @@ abstract class FormatWorkerAction : WorkAction<FormatWorkerParameters> {
             },
         )
     }
-
-    private fun formatKt(file: File, ruleSets: Set<RuleProvider>, onError: ErrorHandler) =
-        format(file, ruleSets, onError, false)
-
-    private fun formatKts(file: File, ruleSets: Set<RuleProvider>, onError: ErrorHandler) =
-        format(file, ruleSets, onError, true)
-
-    private fun format(file: File, ruleProviders: Set<RuleProvider>, onError: ErrorHandler, script: Boolean): String {
-        return KtLint.format(
-            KtLint.ExperimentalParams(
-                fileName = file.path,
-                text = file.readText(),
-                ruleProviders = ruleProviders,
-                script = script,
-                editorConfigOverride = editorConfigOverride(ktLintParams),
-                cb = { error, corrected ->
-                    onError(error, corrected)
-                },
-            ),
-        )
-    }
 }
 
-typealias ErrorHandler = (error: LintError, corrected: Boolean) -> Unit
+private val supportedExtensions = setOf("kt", "kts")
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 914792a..01f210a 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
@@ -1,9 +1,7 @@
 package org.jmailen.gradle.kotlinter.tasks.lint
 
-import com.pinterest.ktlint.core.KtLint
-import com.pinterest.ktlint.core.LintError
+import com.pinterest.ktlint.core.Code
 import com.pinterest.ktlint.core.Reporter
-import com.pinterest.ktlint.core.RuleProvider
 import org.gradle.api.logging.Logger
 import org.gradle.api.logging.Logging
 import org.gradle.internal.logging.slf4j.DefaultContextAwareTaskLogger
@@ -11,12 +9,10 @@ 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.LintFailure
-import org.jmailen.gradle.kotlinter.support.defaultRuleSetProviders
-import org.jmailen.gradle.kotlinter.support.editorConfigOverride
+import org.jmailen.gradle.kotlinter.support.createKtlintEngine
 import org.jmailen.gradle.kotlinter.support.reporterFor
 import org.jmailen.gradle.kotlinter.support.reporterPathFor
 import org.jmailen.gradle.kotlinter.support.resetEditorconfigCacheIfNeeded
-import org.jmailen.gradle.kotlinter.support.resolveRuleProviders
 import org.jmailen.gradle.kotlinter.tasks.LintTask
 import java.io.File
 
@@ -31,28 +27,27 @@ abstract class LintWorkerAction : WorkAction<LintWorkerParameters> {
     private val ktLintParams: KtLintParams = parameters.ktLintParams.get()
 
     override fun execute() {
-        resetEditorconfigCacheIfNeeded(
-            changedEditorconfigFiles = parameters.changedEditorconfigFiles,
+        val ktLintEngine = createKtlintEngine(ktLintParams = ktLintParams)
+        ktLintEngine.resetEditorconfigCacheIfNeeded(
+            changedEditorconfigFiles = parameters.changedEditorConfigFiles,
             logger = logger,
         )
+
         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")
-                val lintFunc = when (file.extension) {
-                    "kt" -> ::lintKt
-                    "kts" -> ::lintKts
-                    else -> {
-                        logger.debug("$name ignoring non Kotlin file: $relativePath")
-                        null
-                    }
+
+                if (file.extension !in supportedExtensions) {
+                    logger.debug("$name ignoring non Kotlin file: $relativePath")
+                    return@forEach
                 }
-                lintFunc?.invoke(file, ruleSets) { error ->
+
+                ktLintEngine.lint(Code.CodeFile(file)) { error ->
                     hasError = true
                     reporters.onEach { reporter ->
                         // some reporters want relative paths, some want absolute
@@ -72,24 +67,6 @@ abstract class LintWorkerAction : WorkAction<LintWorkerParameters> {
             throw LintFailure("kotlin source failed lint check")
         }
     }
-
-    private fun lintKt(file: File, ruleSets: Set<RuleProvider>, onError: (error: LintError) -> Unit) =
-        lint(file, ruleSets, onError, false)
-
-    private fun lintKts(file: File, ruleSets: Set<RuleProvider>, onError: (error: LintError) -> Unit) =
-        lint(file, ruleSets, onError, true)
-
-    private fun lint(file: File, ruleProviders: Set<RuleProvider>, onError: ErrorHandler, script: Boolean) =
-        KtLint.lint(
-            KtLint.ExperimentalParams(
-                fileName = file.path,
-                text = file.readText(),
-                ruleProviders = ruleProviders,
-                script = script,
-                editorConfigOverride = editorConfigOverride(ktLintParams),
-                cb = { error, _ -> onError(error) },
-            ),
-        )
 }
 
-typealias ErrorHandler = (error: LintError) -> Unit
+private val supportedExtensions = setOf("kt", "kts")
diff --git a/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/lint/LintWorkerParameters.kt b/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/lint/LintWorkerParameters.kt
index 82b375b..f060af6 100644
--- a/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/lint/LintWorkerParameters.kt
+++ b/src/main/kotlin/org/jmailen/gradle/kotlinter/tasks/lint/LintWorkerParameters.kt
@@ -10,7 +10,7 @@ import java.io.File
 
 interface LintWorkerParameters : WorkParameters {
     val name: Property<String>
-    val changedEditorconfigFiles: ConfigurableFileCollection
+    val changedEditorConfigFiles: ConfigurableFileCollection
     val files: ConfigurableFileCollection
     val projectDirectory: RegularFileProperty
     val reporters: MapProperty<String, File>


Test patch:
diff --git a/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/EditorConfigTest.kt b/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/EditorConfigTest.kt
index 7e96c65..9791281 100644
--- a/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/EditorConfigTest.kt
+++ b/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/EditorConfigTest.kt
@@ -60,7 +60,7 @@ internal class EditorConfigTest : WithGradleTest.Kotlin() {
                 // language=editorconfig
                 """
                     [*.{kt,kts}]
-                    ktlint_disabled_rules = filename
+                    ktlint_standard_filename = disabled
                 """.trimIndent(),
             )
         }
@@ -111,7 +111,7 @@ internal class EditorConfigTest : WithGradleTest.Kotlin() {
                 // language=editorconfig
                 """
                     [*.{kt,kts}]
-                    ktlint_disabled_rules = filename
+                    ktlint_standard_filename = disabled
                 """.trimIndent(),
             )
         }
@@ -164,7 +164,7 @@ internal class EditorConfigTest : WithGradleTest.Kotlin() {
                 // language=editorconfig
                 """
                     [*.{kt,kts}]
-                    ktlint_disabled_rules = filename
+                    ktlint_standard_filename = disabled
                 """.trimIndent(),
             )
         }
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 42521ed..5b7867c 100644
--- a/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/ExtensionTest.kt
+++ b/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/ExtensionTest.kt
@@ -84,7 +84,8 @@ internal class ExtensionTest : WithGradleTest.Kotlin() {
             val script =
                 """
                 kotlinter {
-                    disabledRules = ["filename"]
+                    experimentalRules = true
+                    disabledRules = ["filename", "experimental:unnecessary-parentheses-before-trailing-lambda"]
                 }
                 """.trimIndent()
             appendText(script)
@@ -92,6 +93,14 @@ internal class ExtensionTest : WithGradleTest.Kotlin() {
         projectRoot.resolve("src/main/kotlin/FileName.kt") {
             writeText(kotlinClass("DifferentClassName"))
         }
+        projectRoot.resolve("src/main/kotlin/UnnecessaryParentheses.kt") {
+            writeText(
+                """
+                val FAILING = "should not have '()'".count() { it == 'x' }
+
+                """.trimIndent(),
+            )
+        }
 
         build("lintKotlin").apply {
             assertEquals(TaskOutcome.SUCCESS, task(":lintKotlinMain")?.outcome)
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 f1d0def..eb86d3f 100644
--- a/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/KotlinProjectTest.kt
+++ b/src/test/kotlin/org/jmailen/gradle/kotlinter/functional/KotlinProjectTest.kt
@@ -55,6 +55,32 @@ internal class KotlinProjectTest : WithGradleTest.Kotlin() {
         }
     }
 
+    @Test
+    fun `lintKotlinMain fails when lint errors for experimental rules are detected`() {
+        settingsFile()
+        buildFile()
+
+        buildFile.appendText(
+            """
+            
+            kotlinter {
+                experimentalRules = true
+            }
+            """.trimIndent(),
+        )
+
+        fileWithFailingExperimentalRule()
+
+        buildAndFail("lintKotlinMain").apply {
+            assertTrue(output.contains(".*Lint error > \\[experimental:unnecessary-parentheses".toRegex()))
+            output.lines().filter { it.contains("Lint error") }.forEach { line ->
+                val filePath = pathPattern.find(line)?.groups?.get(1)?.value.orEmpty()
+                assertTrue(File(filePath).exists())
+            }
+            assertEquals(FAILED, task(":lintKotlinMain")?.outcome)
+        }
+    }
+
     @Test
     fun `lintKotlinMain succeeds when no lint errors detected`() {
         settingsFile()
@@ -76,6 +102,18 @@ internal class KotlinProjectTest : WithGradleTest.Kotlin() {
         }
     }
 
+    @Test
+    fun `lintKotlinMain succeeds when experimental rules are not enabled and code contains experimental rules violations`() {
+        settingsFile()
+        buildFile()
+
+        fileWithFailingExperimentalRule()
+
+        build("lintKotlinMain").apply {
+            assertEquals(SUCCESS, task(":lintKotlinMain")?.outcome)
+        }
+    }
+
     @Test
     fun `formatKotlin reports formatted and unformatted files`() {
         settingsFile()
@@ -213,4 +251,14 @@ internal class KotlinProjectTest : WithGradleTest.Kotlin() {
     private fun kotlinSourceFile(name: String, content: String) = File(sourceDir, name).apply {
         writeText(content)
     }
+
+    private fun fileWithFailingExperimentalRule() {
+        kotlinSourceFile(
+            "ExperimentalRuleViolations.kt",
+            """
+            val variable = "should not contain '()'".count() { it == 'x' }
+    
+            """.trimIndent(),
+        )
+    }
 }
diff --git a/src/test/kotlin/org/jmailen/gradle/kotlinter/support/RuleSetsTest.kt b/src/test/kotlin/org/jmailen/gradle/kotlinter/support/RuleSetsTest.kt
index 033989d..b06e86d 100644
--- a/src/test/kotlin/org/jmailen/gradle/kotlinter/support/RuleSetsTest.kt
+++ b/src/test/kotlin/org/jmailen/gradle/kotlinter/support/RuleSetsTest.kt
@@ -1,6 +1,7 @@
 package org.jmailen.gradle.kotlinter.support
 
-import com.pinterest.ktlint.core.KtLint
+import com.pinterest.ktlint.core.Code
+import com.pinterest.ktlint.core.KtLintRuleEngine
 import com.pinterest.ktlint.core.Rule
 import com.pinterest.ktlint.core.RuleProvider
 import com.pinterest.ktlint.core.RuleSetProviderV2
@@ -12,11 +13,9 @@ class RuleSetsTest {
 
     @Test
     fun `resolveRuleSets loads from classpath providers`() {
-        val standardOnly = resolveRuleProviders(defaultRuleSetProviders, includeExperimentalRules = false)
-        val withExperimentalRules = resolveRuleProviders(defaultRuleSetProviders, includeExperimentalRules = true)
+        val rules = resolveRuleProviders(defaultRuleSetProviders)
 
-        assertTrue(standardOnly.isNotEmpty())
-        assertTrue(standardOnly.size < withExperimentalRules.size)
+        assertTrue(rules.isNotEmpty())
     }
 
     @Test
@@ -34,22 +33,22 @@ class RuleSetsTest {
 
     @Test
     fun `test compatibility`() {
-        KtLint.lint(
-            KtLint.ExperimentalParams(
-                fileName = "/tmp/src/test/KotlinClass.kt",
-                text = """
-                    package test
+        KtLintRuleEngine(
+            ruleProviders = resolveRuleProviders(defaultRuleSetProviders),
+        ).lint(
+            Code.CodeSnippet(
+                """
+                package test
 
-                    class KotlinClass {
-                        private fun hi() {
-                            println("hi")
-                        }
+                class KotlinClass {
+                    private fun hi() {
+                        println("hi")
                     }
+                }
 
                 """.trimIndent(),
-                ruleProviders = resolveRuleProviders(defaultRuleSetProviders),
-                cb = { _, _ -> },
             ),
+            callback = { _ -> },
         )
     }
 }