diff --git a/CHANGELOG.md b/CHANGELOG.md index f1dcb234..638f14f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Added + +- Add support for reading ktlint version from `ktlint-plugins.properties` file [#1001](https://github.com/JLLeitschuh/ktlint-gradle/pull/1001) + - Enables automatic synchronization of ktlint version between IntelliJ plugin and Gradle builds + - When `ktlint-plugins.properties` exists in project root with `ktlint-version` property, that version is used as default + - Explicit version configuration in build script still takes precedence + ## [14.0.1] - 2025-11-10 - Update build to work with gradle 9.1 and Java 25 [#962](https://github.com/JLLeitschuh/ktlint-gradle/pull/962) diff --git a/README.md b/README.md index 29cc993a..21496e85 100644 --- a/README.md +++ b/README.md @@ -279,6 +279,44 @@ ktlint { ``` +#### KtLint IntelliJ Plugin Integration + +If you're using the [ktlint IntelliJ plugin](https://github.com/nbadal/ktlint-intellij-plugin), you can automatically synchronize the ktlint version between your IDE and Gradle builds by creating a `ktlint-plugins.properties` file in your project root directory. + +The IntelliJ plugin can read and write the ktlint version to this file. When this file exists and contains a `ktlint-version` property, the Gradle plugin will automatically use that version as the default. + +Example `ktlint-plugins.properties` file: +```properties +ktlint-version=1.2.1 +``` + +**Key benefits:** +- Ensures consistency between IDE and build tool ktlint versions +- Simplifies version management across your development team +- Allows IntelliJ plugin to manage the version + +**Override behavior:** +Even when the `ktlint-plugins.properties` file exists, you can still explicitly set the version in your `build.gradle` or `build.gradle.kts` file, which will take precedence: + +
+Groovy + +```groovy +ktlint { + version = "1.3.0" // This overrides the version in ktlint-plugins.properties +} +``` +
+
+Kotlin + +```kotlin +ktlint { + version.set("1.3.0") // This overrides the version in ktlint-plugins.properties +} +``` +
+ ### Configuration The following configuration block is _optional_. diff --git a/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/KtlintExtension.kt b/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/KtlintExtension.kt index 7bdee57e..217a69f2 100644 --- a/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/KtlintExtension.kt +++ b/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/KtlintExtension.kt @@ -31,8 +31,19 @@ open class KtlintExtension @Inject internal constructor( /** * The version of KtLint to use. + * + * If a `ktlint-plugins.properties` file exists in the project root with a `ktlint-version` property, + * that version will be used as the default. Otherwise, defaults to "1.5.0". + * + * This property can be explicitly set in the build script to override the default behavior. */ - val version: Property = objectFactory.property { set("1.5.0") } + val version: Property = objectFactory.property { + // Try to read version from ktlint-plugins.properties file first + val versionFromFile = readKtlintVersionFromPropertiesFile( + projectLayout.projectDirectory.asFile.toPath() + ) + set(versionFromFile ?: "1.5.0") + } /** * Enable relative paths in reports diff --git a/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/PluginUtil.kt b/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/PluginUtil.kt index c9d7dd8e..b8edc230 100644 --- a/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/PluginUtil.kt +++ b/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/PluginUtil.kt @@ -33,6 +33,28 @@ internal inline fun Project.registerTask( } internal const val EDITOR_CONFIG_FILE_NAME = ".editorconfig" +internal const val KTLINT_PLUGINS_PROPERTIES_FILE_NAME = "ktlint-plugins.properties" +internal const val KTLINT_PLUGINS_VERSION_PROPERTY = "ktlint-version" + +/** + * Reads the ktlint version from the ktlint-plugins.properties file if it exists. + * This file is used by the ktlint IntelliJ plugin to store the ktlint version. + * + * @param projectDir The project directory to search for the properties file + * @return The ktlint version string if found, null otherwise + */ +internal fun readKtlintVersionFromPropertiesFile(projectDir: Path): String? { + val propertiesFile = projectDir.resolve(KTLINT_PLUGINS_PROPERTIES_FILE_NAME) + if (!Files.exists(propertiesFile) || !Files.isReadable(propertiesFile)) { + return null + } + + return propertiesFile.toFile().inputStream().use { input -> + val properties = java.util.Properties() + properties.load(input) + properties.getProperty(KTLINT_PLUGINS_VERSION_PROPERTY)?.takeIf { it.isNotBlank() } + } +} internal fun getEditorConfigFiles(currentProjectDir: Path): Set { val result = mutableSetOf() diff --git a/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/KtlintPluginsPropertiesTest.kt b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/KtlintPluginsPropertiesTest.kt new file mode 100644 index 00000000..7f5c42e1 --- /dev/null +++ b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/KtlintPluginsPropertiesTest.kt @@ -0,0 +1,149 @@ +package org.jlleitschuh.gradle.ktlint + +import org.assertj.core.api.Assertions.assertThat +import org.gradle.util.GradleVersion +import org.jlleitschuh.gradle.ktlint.testdsl.CommonTest +import org.jlleitschuh.gradle.ktlint.testdsl.GradleTestVersions +import org.jlleitschuh.gradle.ktlint.testdsl.build +import org.jlleitschuh.gradle.ktlint.testdsl.project + +@GradleTestVersions +class KtlintPluginsPropertiesTest : AbstractPluginTest() { + + @CommonTest + fun shouldUseVersionFromKtlintPluginsPropertiesFileWhenPresent(gradleVersion: GradleVersion) { + project(gradleVersion) { + // Create ktlint-plugins.properties file with version + projectPath.resolve(KTLINT_PLUGINS_PROPERTIES_FILE_NAME).writeText( + """ + ktlint-version=1.2.1 + """.trimIndent() + ) + + withCleanSources() + + buildGradle.appendText( + """ + tasks.register("printKtlintVersion") { + doLast { + println("Ktlint version: " + ktlint.version.get()) + } + } + """.trimIndent() + ) + + build("printKtlintVersion") { + assertThat(output).contains("Ktlint version: 1.2.1") + } + } + } + + @CommonTest + fun shouldUseDefaultVersionWhenKtlintPluginsPropertiesFileIsAbsent(gradleVersion: GradleVersion) { + project(gradleVersion) { + withCleanSources() + + buildGradle.appendText( + """ + tasks.register("printKtlintVersion") { + doLast { + println("Ktlint version: " + ktlint.version.get()) + } + } + """.trimIndent() + ) + + build("printKtlintVersion") { + assertThat(output).contains("Ktlint version: 1.5.0") + } + } + } + + @CommonTest + fun shouldAllowExplicitVersionOverrideEvenWhenPropertiesFileExists(gradleVersion: GradleVersion) { + project(gradleVersion) { + // Create ktlint-plugins.properties file with version + projectPath.resolve(KTLINT_PLUGINS_PROPERTIES_FILE_NAME).writeText( + """ + ktlint-version=1.2.1 + """.trimIndent() + ) + + withCleanSources() + + buildGradle.appendText( + """ + ktlint { + version = "1.3.0" + } + + tasks.register("printKtlintVersion") { + doLast { + println("Ktlint version: " + ktlint.version.get()) + } + } + """.trimIndent() + ) + + build("printKtlintVersion") { + assertThat(output).contains("Ktlint version: 1.3.0") + } + } + } + + @CommonTest + fun shouldUseDefaultVersionWhenKtlintVersionPropertyIsBlank(gradleVersion: GradleVersion) { + project(gradleVersion) { + // Create ktlint-plugins.properties file with blank version + projectPath.resolve(KTLINT_PLUGINS_PROPERTIES_FILE_NAME).writeText( + """ + ktlint-version= + """.trimIndent() + ) + + withCleanSources() + + buildGradle.appendText( + """ + tasks.register("printKtlintVersion") { + doLast { + println("Ktlint version: " + ktlint.version.get()) + } + } + """.trimIndent() + ) + + build("printKtlintVersion") { + assertThat(output).contains("Ktlint version: 1.5.0") + } + } + } + + @CommonTest + fun shouldUseDefaultVersionWhenPropertiesFileHasNoKtlintVersionProperty(gradleVersion: GradleVersion) { + project(gradleVersion) { + // Create ktlint-plugins.properties file without ktlint-version + projectPath.resolve(KTLINT_PLUGINS_PROPERTIES_FILE_NAME).writeText( + """ + some-other-property=value + """.trimIndent() + ) + + withCleanSources() + + buildGradle.appendText( + """ + tasks.register("printKtlintVersion") { + doLast { + println("Ktlint version: " + ktlint.version.get()) + } + } + """.trimIndent() + ) + + build("printKtlintVersion") { + assertThat(output).contains("Ktlint version: 1.5.0") + } + } + } +} diff --git a/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/PluginUtilTest.kt b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/PluginUtilTest.kt index 43ef281b..4febaa90 100644 --- a/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/PluginUtilTest.kt +++ b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/PluginUtilTest.kt @@ -40,4 +40,99 @@ internal class PluginUtilTest { delete() } } + + @Test + fun `test readKtlintVersionFromPropertiesFile returns version when file exists`() { + temporaryFolder.resolve(KTLINT_PLUGINS_PROPERTIES_FILE_NAME).apply { + createNewFile() + writeText( + """ + ktlint-version=1.2.1 + """.trimIndent() + ) + val version = readKtlintVersionFromPropertiesFile(temporaryFolder.toPath()) + Assertions.assertEquals("1.2.1", version) { + "correctly reads version from properties file" + } + delete() + } + } + + @Test + fun `test readKtlintVersionFromPropertiesFile returns null when file does not exist`() { + val version = readKtlintVersionFromPropertiesFile(temporaryFolder.toPath()) + Assertions.assertNull(version) { + "returns null when properties file does not exist" + } + } + + @Test + fun `test readKtlintVersionFromPropertiesFile returns null when version property is missing`() { + temporaryFolder.resolve(KTLINT_PLUGINS_PROPERTIES_FILE_NAME).apply { + createNewFile() + writeText( + """ + some-other-property=value + """.trimIndent() + ) + val version = readKtlintVersionFromPropertiesFile(temporaryFolder.toPath()) + Assertions.assertNull(version) { + "returns null when ktlint-version property is missing" + } + delete() + } + } + + @Test + fun `test readKtlintVersionFromPropertiesFile handles whitespace correctly`() { + temporaryFolder.resolve(KTLINT_PLUGINS_PROPERTIES_FILE_NAME).apply { + createNewFile() + writeText( + """ + ktlint-version = 1.3.0 + """.trimIndent() + ) + val version = readKtlintVersionFromPropertiesFile(temporaryFolder.toPath()) + Assertions.assertEquals("1.3.0", version) { + "correctly handles whitespace around equals sign" + } + delete() + } + } + + @Test + fun `test readKtlintVersionFromPropertiesFile returns null when version is blank`() { + temporaryFolder.resolve(KTLINT_PLUGINS_PROPERTIES_FILE_NAME).apply { + createNewFile() + writeText( + """ + ktlint-version= + """.trimIndent() + ) + val version = readKtlintVersionFromPropertiesFile(temporaryFolder.toPath()) + Assertions.assertNull(version) { + "returns null when ktlint-version is blank" + } + delete() + } + } + + @Test + fun `test readKtlintVersionFromPropertiesFile handles multiple properties`() { + temporaryFolder.resolve(KTLINT_PLUGINS_PROPERTIES_FILE_NAME).apply { + createNewFile() + writeText( + """ + some-property=value1 + ktlint-version=1.4.0 + another-property=value2 + """.trimIndent() + ) + val version = readKtlintVersionFromPropertiesFile(temporaryFolder.toPath()) + Assertions.assertEquals("1.4.0", version) { + "correctly finds ktlint-version among multiple properties" + } + delete() + } + } }