Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,44 @@ ktlint {
```
</details>

#### 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:

<details>
<summary>Groovy</summary>

```groovy
ktlint {
version = "1.3.0" // This overrides the version in ktlint-plugins.properties
}
```
</details>
<details open>
<summary>Kotlin</summary>

```kotlin
ktlint {
version.set("1.3.0") // This overrides the version in ktlint-plugins.properties
}
```
</details>

### Configuration
The following configuration block is _optional_.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = objectFactory.property { set("1.5.0") }
val version: Property<String> = 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
Expand Down
22 changes: 22 additions & 0 deletions plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/PluginUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,28 @@ internal inline fun <reified T : Task> 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<Path> {
val result = mutableSetOf<Path>()
Expand Down
Original file line number Diff line number Diff line change
@@ -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")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
}
Loading