diff --git a/CHANGES.md b/CHANGES.md
index 66bd56355a..d902cfc748 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -18,6 +18,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
- Bump default `palantir-java-format` version `2.98.0` -> `2.99.0`. ([#3103](https://github.com/diffplug/spotless/pull/3103))
- Bump default `mockito-core` version `5.23.0` -> `5.24.0`. ([#3106](https://github.com/diffplug/spotless/pull/3106))
- Bump default `greclipse` version to latest `4.40` -> `4.41`. ([#3123](https://github.com/diffplug/spotless/pull/3123))
+- Bump default `ktfmt` version `0.64` -> `0.65`. ([#3135](https://github.com/diffplug/spotless/pull/3135))
+ Which moved to the `org.jetbrains.kotlinx:ktfmt` coordinate and `org.jetbrains.kotlinx.ktfmt` package. Older versions are still supported.
### Fixed
- `shortenFullyQualifiedTypes` leaves a qualified name alone when the enclosing type extends or implements a type that is not in the same file, so an inherited member type cannot take the simple name. ([#3117](https://github.com/diffplug/spotless/issues/3117))
diff --git a/build-logic/src/main/kotlin/spotless.spotless-conventions.gradle.kts b/build-logic/src/main/kotlin/spotless.spotless-conventions.gradle.kts
index 86f5fbe9ce..398b576af3 100644
--- a/build-logic/src/main/kotlin/spotless.spotless-conventions.gradle.kts
+++ b/build-logic/src/main/kotlin/spotless.spotless-conventions.gradle.kts
@@ -2,6 +2,10 @@ plugins {
id("com.diffplug.spotless")
}
+// The released Spotless plugin used here only knows the legacy `com.facebook:ktfmt` coordinate.
+// TODO: switch back to `libs.ktfmt` once a Spotless release supporting ktfmt 0.65+ is used.
+val KTFMT_VERSION = "0.64"
+
spotless {
if (project != rootProject) {
java {
@@ -23,11 +27,11 @@ spotless {
}
kotlin {
target("build-logic/src/**/*.kt")
- ktfmt(libs.ktfmt.get().version)
+ ktfmt(KTFMT_VERSION)
}
kotlinGradle {
target("*.gradle.kts", "build-logic/*.gradle.kts", "build-logic/src/**/*.gradle.kts")
- ktfmt(libs.ktfmt.get().version)
+ ktfmt(KTFMT_VERSION)
}
groovyGradle {
target("*.gradle", "gradle/*.gradle")
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index a9c36d9206..41ce0c4b94 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -55,7 +55,7 @@ gherkin-utils = "io.cucumber:gherkin-utils:12.0.2"
google-java-format = "com.google.googlejavaformat:google-java-format:1.36.1"
gson = "com.google.code.gson:gson:2.14.0"
json-simple = "org.json:json:20210307"
-ktfmt = "com.facebook:ktfmt:0.64"
+ktfmt = "org.jetbrains.kotlinx:ktfmt:0.65"
prince-of-space-core = "io.github.agustafson.princeofspace:prince-of-space-core:2.2.0"
scalafmt-core = "org.scalameta:scalafmt-core_2.13:3.11.5"
sortpom-sorter = "com.github.ekryd.sortpom:sortpom-sorter:4.0.0"
diff --git a/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtFormatterFunc.java b/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtFormatterFunc.java
index b99af17015..32db66f126 100644
--- a/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtFormatterFunc.java
+++ b/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtFormatterFunc.java
@@ -15,11 +15,15 @@
*/
package com.diffplug.spotless.glue.ktfmt;
+import java.io.File;
+
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import com.facebook.ktfmt.format.Formatter;
-import com.facebook.ktfmt.format.FormattingOptions;
+import org.jetbrains.kotlinx.ktfmt.format.FileType;
+import org.jetbrains.kotlinx.ktfmt.format.Formatter;
+import org.jetbrains.kotlinx.ktfmt.format.FormattingOptions;
+import org.jetbrains.kotlinx.ktfmt.format.KotlinCode;
import com.diffplug.spotless.FormatterFunc;
@@ -50,15 +54,26 @@ public KtfmtFormatterFunc(@Nonnull KtfmtStyle style, @Nullable KtfmtFormattingOp
@Nonnull
@Override
public String apply(@Nonnull String input) throws Exception {
- return Formatter.format(createFormattingOptions(), input);
+ return format(input, FileType.REGULAR);
+ }
+
+ @Nonnull
+ @Override
+ public String apply(@Nonnull String input, @Nonnull File file) throws Exception {
+ FileType fileType = file.getName().endsWith("." + FileType.SCRIPT.getExtension()) ? FileType.SCRIPT : FileType.REGULAR;
+ return format(input, fileType);
+ }
+
+ private String format(String input, FileType fileType) throws Exception {
+ return Formatter.format(createFormattingOptions(), KotlinCode.Companion.from(input, fileType));
}
private FormattingOptions createFormattingOptions() throws Exception {
FormattingOptions formattingOptions = switch (style) {
- case META -> Formatter.META_FORMAT;
- case GOOGLE -> Formatter.GOOGLE_FORMAT;
- case KOTLIN_LANG -> Formatter.KOTLINLANG_FORMAT;
- default -> throw new IllegalStateException("Unknown formatting option " + style);
+ case META -> Formatter.META_FORMAT;
+ case GOOGLE -> Formatter.GOOGLE_FORMAT;
+ case KOTLIN_LANG -> Formatter.KOTLINLANG_FORMAT;
+ default -> throw new IllegalStateException("Unknown formatting option " + style);
};
if (ktfmtFormattingOptions == null) {
diff --git a/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtTrailingCommaManagementStrategy.java b/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtTrailingCommaManagementStrategy.java
index f9ca437eee..3816d88e87 100644
--- a/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtTrailingCommaManagementStrategy.java
+++ b/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtTrailingCommaManagementStrategy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 DiffPlug
+ * Copyright 2025-2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,16 +15,16 @@
*/
package com.diffplug.spotless.glue.ktfmt;
-import com.facebook.ktfmt.format.TrailingCommaManagementStrategy;
+import org.jetbrains.kotlinx.ktfmt.format.TrailingCommaManagementStrategy;
public enum KtfmtTrailingCommaManagementStrategy {
NONE, ONLY_ADD, COMPLETE;
public TrailingCommaManagementStrategy toFormatterTrailingCommaManagementStrategy() {
return switch (this) {
- case NONE -> TrailingCommaManagementStrategy.NONE;
- case ONLY_ADD -> TrailingCommaManagementStrategy.ONLY_ADD;
- case COMPLETE -> TrailingCommaManagementStrategy.COMPLETE;
+ case NONE -> TrailingCommaManagementStrategy.NONE;
+ case ONLY_ADD -> TrailingCommaManagementStrategy.ONLY_ADD;
+ case COMPLETE -> TrailingCommaManagementStrategy.COMPLETE;
};
}
}
diff --git a/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java b/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java
index 1984b58097..c3e63f7447 100644
--- a/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java
+++ b/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java
@@ -36,14 +36,18 @@
import com.diffplug.spotless.ThrowingEx;
/**
- * Wraps up ktfmt as a FormatterStep.
+ * Wraps up ktfmt as a FormatterStep.
*/
public final class KtfmtStep implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private static final String NAME = "ktfmt";
- private static final String MAVEN_COORDINATE = "com.facebook:ktfmt:";
+ /**
+ * Since 0.65, ktfmt is published under the {@code org.jetbrains.kotlinx} group with the {@code org.jetbrains.kotlinx.ktfmt} package.
+ */
+ private static final String MAVEN_COORDINATE = "org.jetbrains.kotlinx:ktfmt:";
+ private static final String MAVEN_COORDINATE_LEGACY = "com.facebook:ktfmt:";
private final String version;
/**
@@ -69,7 +73,7 @@ private KtfmtStep(String version,
/**
* Used to allow multiple style option through formatting options and since when is each of them available.
*
- * @see ktfmt source
+ * @see ktfmt source
*/
public enum Style {
// @formatter:off
@@ -213,7 +217,7 @@ public static FormatterStep create(String version, Provisioner provisioner, @Nul
Objects.requireNonNull(version, "version");
Objects.requireNonNull(provisioner, "provisioner");
return FormatterStep.create(NAME,
- new KtfmtStep(version, JarState.promise(() -> JarState.from(MAVEN_COORDINATE + version, provisioner)), style, options),
+ new KtfmtStep(version, JarState.promise(() -> JarState.from(mavenCoordinate(version) + version, provisioner)), style, options),
KtfmtStep::equalityState,
State::createFormat);
}
@@ -222,6 +226,17 @@ public static String defaultVersion() {
return KotlinBuildConfig.VERSION_KTFMT;
}
+ private static String mavenCoordinate(String version) {
+ return isLegacyPackage(version) ? MAVEN_COORDINATE_LEGACY : MAVEN_COORDINATE;
+ }
+
+ /**
+ * Versions before 0.65 use the {@code com.facebook} group and package, so they can't use the glue code compiled against the latest ktfmt.
+ */
+ private static boolean isLegacyPackage(String version) {
+ return BadSemver.version(version) < BadSemver.version(0, 65);
+ }
+
private State equalityState() {
return new State(version, jarState.get(), style, options);
}
@@ -249,11 +264,7 @@ private static final class State implements Serializable {
FormatterFunc createFormat() throws Exception {
final ClassLoader classLoader = jarState.getClassLoader();
- if (BadSemver.version(version) < BadSemver.version(0, 51)) {
- return new KtfmtFormatterFuncCompat(version, style, options, classLoader).getFormatterFunc();
- }
-
- if (options != null && BadSemver.version(version) < BadSemver.version(0, 63)) {
+ if (isLegacyPackage(version)) {
return new KtfmtFormatterFuncCompat(version, style, options, classLoader).getFormatterFunc();
}
@@ -342,13 +353,16 @@ private String getKtfmtStyleOption(Style style) {
}
}
+ /**
+ * Reflection-based formatter for ktfmt versions before 0.65, which use the {@code com.facebook.ktfmt} package.
+ */
private static final class KtfmtFormatterFuncCompat {
private static final String PACKAGE = "com.facebook.ktfmt";
/**
* The format method is available in the link below.
*
- * @see ktfmt source
+ * @see ktfmt source
*/
static final String FORMATTER_METHOD = "format";
@@ -430,7 +444,7 @@ private Object getCustomFormattingOptions(Class> formatterClass) throws Except
/* manageTrailingCommas = */ Objects.requireNonNullElse(getManageTrailingCommasFrom(options.trailingCommaManagementStrategy), (Boolean) formattingOptionsClass.getMethod("getManageTrailingCommas").invoke(formattingOptions)),
/* removeUnusedImports = */ Objects.requireNonNullElse(options.removeUnusedImports, (Boolean) formattingOptionsClass.getMethod("getRemoveUnusedImports").invoke(formattingOptions)),
/* debuggingPrintOpsAfterFormatting = */ (Boolean) formattingOptionsClass.getMethod("getDebuggingPrintOpsAfterFormatting").invoke(formattingOptions));
- } else {
+ } else if (BadSemver.version(version) < BadSemver.version(0, 63)) {
Class> trailingCommaManagementStrategyClass = getTrailingCommaManagementStrategyClazz();
Object trailingCommaManagementStrategy = options.trailingCommaManagementStrategy == null
? formattingOptionsClass.getMethod("getTrailingCommaManagementStrategy").invoke(formattingOptions)
@@ -442,6 +456,28 @@ private Object getCustomFormattingOptions(Class> formatterClass) throws Except
/* trailingCommaManagementStrategy = */ trailingCommaManagementStrategy,
/* removeUnusedImports = */ Objects.requireNonNullElse(options.removeUnusedImports, (Boolean) formattingOptionsClass.getMethod("getRemoveUnusedImports").invoke(formattingOptions)),
/* debuggingPrintOpsAfterFormatting = */ (Boolean) formattingOptionsClass.getMethod("getDebuggingPrintOpsAfterFormatting").invoke(formattingOptions));
+ } else {
+ // FormattingOptions.Builder is available since 0.63, and keeps the options not managed here (e.g. preserveLambdaBreaks) from the style.
+ Object builder = formattingOptionsClass.getMethod("toBuilder").invoke(formattingOptions);
+ Class> builderClass = builder.getClass();
+ if (options.maxWidth != null) {
+ builderClass.getMethod("maxWidth", int.class).invoke(builder, options.maxWidth);
+ }
+ if (options.blockIndent != null) {
+ builderClass.getMethod("blockIndent", int.class).invoke(builder, options.blockIndent);
+ }
+ if (options.continuationIndent != null) {
+ builderClass.getMethod("continuationIndent", int.class).invoke(builder, options.continuationIndent);
+ }
+ if (options.trailingCommaManagementStrategy != null) {
+ Class> trailingCommaManagementStrategyClass = getTrailingCommaManagementStrategyClazz();
+ builderClass.getMethod("trailingCommaManagementStrategy", trailingCommaManagementStrategyClass).invoke(builder,
+ Enum.valueOf((Class extends Enum>) trailingCommaManagementStrategyClass, options.trailingCommaManagementStrategy.name()));
+ }
+ if (options.removeUnusedImports != null) {
+ builderClass.getMethod("removeUnusedImports", boolean.class).invoke(builder, options.removeUnusedImports);
+ }
+ formattingOptions = builderClass.getMethod("build").invoke(builder);
}
}
diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md
index ad18e213ca..a053b13358 100644
--- a/plugin-gradle/CHANGES.md
+++ b/plugin-gradle/CHANGES.md
@@ -11,6 +11,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
- Bump default `palantir-java-format` version `2.98.0` -> `2.99.0`. ([#3103](https://github.com/diffplug/spotless/pull/3103))
- Bump default `mockito-core` version `5.23.0` -> `5.24.0`. ([#3106](https://github.com/diffplug/spotless/pull/3106))
- Bump default `greclipse` version to latest `4.40` -> `4.41`. ([#3123](https://github.com/diffplug/spotless/pull/3123))
+- Bump default `ktfmt` version `0.64` -> `0.65`. ([#3135](https://github.com/diffplug/spotless/pull/3135))
+ Which moved to the `org.jetbrains.kotlinx:ktfmt` coordinate and `org.jetbrains.kotlinx.ktfmt` package. Older versions are still supported.
### Fixed
- `shortenFullyQualifiedTypes` leaves a qualified name alone when the enclosing type extends or implements a type that is not in the same file, so an inherited member type cannot take the simple name. ([#3117](https://github.com/diffplug/spotless/issues/3117))
diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md
index cba7a85b35..44fbf1a3be 100644
--- a/plugin-gradle/README.md
+++ b/plugin-gradle/README.md
@@ -626,7 +626,7 @@ spotless { // if you are using build.gradle.kts, instead of 'spotless {' use:
### ktfmt
-[homepage](https://github.com/facebook/ktfmt). [changelog](https://github.com/facebook/ktfmt/releases).
+[homepage](https://github.com/Kotlin/ktfmt). [changelog](https://github.com/Kotlin/ktfmt/releases).
```kotlin
spotless {
diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/BaseKotlinExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/BaseKotlinExtension.java
index c3dc731215..2c99624d00 100644
--- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/BaseKotlinExtension.java
+++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/BaseKotlinExtension.java
@@ -57,13 +57,13 @@ public KtlintConfig ktlint(String version) throws IOException {
return new KtlintConfig(version, Collections.emptyMap(), Collections.emptyList());
}
- /** Uses the ktfmt jar to format source code. */
+ /** Uses the ktfmt jar to format source code. */
public KtfmtConfig ktfmt() {
return ktfmt(KtfmtStep.defaultVersion());
}
/**
- * Uses the given version of ktfmt and applies the dropbox style
+ * Uses the given version of ktfmt and applies the dropbox style
* option to format source code.
*/
public KtfmtConfig ktfmt(String version) {
diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md
index 28cfea7f4d..19a9b05091 100644
--- a/plugin-maven/CHANGES.md
+++ b/plugin-maven/CHANGES.md
@@ -11,6 +11,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
- Bump default `palantir-java-format` version `2.98.0` -> `2.99.0`. ([#3103](https://github.com/diffplug/spotless/pull/3103))
- Bump default `mockito-core` version `5.23.0` -> `5.24.0`. ([#3106](https://github.com/diffplug/spotless/pull/3106))
- Bump default `greclipse` version to latest `4.40` -> `4.41`. ([#3123](https://github.com/diffplug/spotless/pull/3123))
+- Bump default `ktfmt` version `0.64` -> `0.65`. ([#3135](https://github.com/diffplug/spotless/pull/3135))
+ Which moved to the `org.jetbrains.kotlinx:ktfmt` coordinate and `org.jetbrains.kotlinx.ktfmt` package. Older versions are still supported.
### Fixed
- Migrate Plexus component injection to JSR-330 and Maven parameter. ([#3122](https://github.com/diffplug/spotless/pull/3122))
diff --git a/plugin-maven/README.md b/plugin-maven/README.md
index 896bfbb61c..c40876d0d5 100644
--- a/plugin-maven/README.md
+++ b/plugin-maven/README.md
@@ -575,7 +575,7 @@ Groovy-Eclipse formatting errors/warnings lead per default to a build failure. T
### ktfmt
-[homepage](https://github.com/facebook/ktfmt). [changelog](https://github.com/facebook/ktfmt/releases). [code](https://github.com/diffplug/spotless/blob/main/plugin-maven/src/main/java/com/diffplug/spotless/maven/kotlin/Ktfmt.java).
+[homepage](https://github.com/Kotlin/ktfmt). [changelog](https://github.com/Kotlin/ktfmt/releases). [code](https://github.com/diffplug/spotless/blob/main/plugin-maven/src/main/java/com/diffplug/spotless/maven/kotlin/Ktfmt.java).
```xml
diff --git a/testlib/src/test/java/com/diffplug/spotless/kotlin/KtfmtStepTest.java b/testlib/src/test/java/com/diffplug/spotless/kotlin/KtfmtStepTest.java
index 0707a8fa09..1492624a9b 100644
--- a/testlib/src/test/java/com/diffplug/spotless/kotlin/KtfmtStepTest.java
+++ b/testlib/src/test/java/com/diffplug/spotless/kotlin/KtfmtStepTest.java
@@ -64,6 +64,20 @@ void behaviorWithOptions_0_62() {
StepHarness.forStep(step).testResource("kotlin/ktfmt/basic.dirty", "kotlin/ktfmt/basic.clean");
}
+ @Test
+ void behavior_0_64() throws Exception {
+ FormatterStep step = KtfmtStep.create("0.64", TestProvisioner.mavenCentral());
+ StepHarness.forStep(step).testResource("kotlin/ktfmt/basic.dirty", "kotlin/ktfmt/basic.clean");
+ }
+
+ @Test
+ void behaviorWithOptions_0_64() {
+ KtfmtStep.KtfmtFormattingOptions options = new KtfmtStep.KtfmtFormattingOptions();
+ options.setMaxWidth(100);
+ FormatterStep step = KtfmtStep.create("0.64", TestProvisioner.mavenCentral(), KtfmtStep.Style.GOOGLE, options);
+ StepHarness.forStep(step).testResource("kotlin/ktfmt/basic.dirty", "kotlin/ktfmt/basic.clean");
+ }
+
@Test
void behaviorWithOptions_0_53() {
KtfmtStep.KtfmtFormattingOptions options = new KtfmtStep.KtfmtFormattingOptions();