Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ This document is intended for Spotless developers.
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).

## [Unreleased]
### Fixed
- Support `ktfmt` 0.63 and use its new builder API for formatting options to better avoid future breaking changes.

## [4.6.2] - 2026-05-27
### Fixed
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ gherkin-utils = "io.cucumber:gherkin-utils:10.0.0"
google-java-format = "com.google.googlejavaformat:google-java-format:1.28.0"
gson = "com.google.code.gson:gson:2.13.2"
javaparser-symbol-solver-core = "com.github.javaparser:javaparser-symbol-solver-core:3.27.1"
ktfmt = "com.facebook:ktfmt:0.61"
ktfmt = "com.facebook:ktfmt:0.63"
palantir-java-format = "com.palantir.javaformat:palantir-java-format:1.1.0"
scalafmt-core = "org.scalameta:scalafmt-core_2.13:3.8.1"
sortpom-sorter = "com.github.ekryd.sortpom:sortpom-sorter:4.0.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022-2025 DiffPlug
* Copyright 2022-2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -61,18 +61,18 @@ private FormattingOptions createFormattingOptions() throws Exception {
default -> throw new IllegalStateException("Unknown formatting option " + style);
};

if (ktfmtFormattingOptions != null) {
formattingOptions = formattingOptions.copy(
ktfmtFormattingOptions.getMaxWidth().orElse(formattingOptions.getMaxWidth()),
ktfmtFormattingOptions.getBlockIndent().orElse(formattingOptions.getBlockIndent()),
ktfmtFormattingOptions.getContinuationIndent().orElse(formattingOptions.getContinuationIndent()),
ktfmtFormattingOptions.getTrailingCommaManagementStrategy()
.map(KtfmtTrailingCommaManagementStrategy::toFormatterTrailingCommaManagementStrategy)
.orElse(formattingOptions.getTrailingCommaManagementStrategy()),
ktfmtFormattingOptions.getRemoveUnusedImports().orElse(formattingOptions.getRemoveUnusedImports()),
formattingOptions.getDebuggingPrintOpsAfterFormatting());
if (ktfmtFormattingOptions == null) {
return formattingOptions;
}

return formattingOptions;
FormattingOptions.Builder builder = formattingOptions.toBuilder();
ktfmtFormattingOptions.getMaxWidth().ifPresent(builder::maxWidth);
ktfmtFormattingOptions.getBlockIndent().ifPresent(builder::blockIndent);
ktfmtFormattingOptions.getContinuationIndent().ifPresent(builder::continuationIndent);
ktfmtFormattingOptions.getTrailingCommaManagementStrategy()
.map(KtfmtTrailingCommaManagementStrategy::toFormatterTrailingCommaManagementStrategy)
.ifPresent(builder::trailingCommaManagementStrategy);
ktfmtFormattingOptions.getRemoveUnusedImports().ifPresent(builder::removeUnusedImports);
return builder.build();
}
}
32 changes: 25 additions & 7 deletions lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
public final class KtfmtStep implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private static final String DEFAULT_VERSION = "0.61";
private static final String DEFAULT_VERSION = "0.63";
private static final String NAME = "ktfmt";
private static final String MAVEN_COORDINATE = "com.facebook:ktfmt:";

Expand Down Expand Up @@ -253,6 +253,10 @@ FormatterFunc createFormat() throws Exception {
return new KtfmtFormatterFuncCompat(version, style, options, classLoader).getFormatterFunc();
}

if (options != null && BadSemver.version(version) < BadSemver.version(0, 63)) {
return new KtfmtFormatterFuncCompat(version, style, options, classLoader).getFormatterFunc();
}

final Class<?> formatterFuncClass = classLoader.loadClass("com.diffplug.spotless.glue.ktfmt.KtfmtFormatterFunc");
final Class<?> ktfmtStyleClass = classLoader.loadClass("com.diffplug.spotless.glue.ktfmt.KtfmtStyle");
final Class<?> ktfmtFormattingOptionsClass = classLoader.loadClass("com.diffplug.spotless.glue.ktfmt.KtfmtFormattingOptions");
Expand Down Expand Up @@ -408,7 +412,7 @@ private Object getCustomFormattingOptions(Class<?> formatterClass) throws Except
/* continuationIndent = */ Objects.requireNonNullElse(options.continuationIndent, (Integer) formattingOptionsClass.getMethod("getContinuationIndent").invoke(formattingOptions)),
/* removeUnusedImports = */ Objects.requireNonNullElse(options.removeUnusedImports, (Boolean) formattingOptionsClass.getMethod("getRemoveUnusedImports").invoke(formattingOptions)),
/* debuggingPrintOpsAfterFormatting = */ (Boolean) formattingOptionsClass.getMethod("getDebuggingPrintOpsAfterFormatting").invoke(formattingOptions));
} else if (BadSemver.version(version) < BadSemver.version(0, 57)) {
} else if (BadSemver.version(version) < BadSemver.version(0, 51)) {
Class<?> styleClass = classLoader.loadClass(formattingOptionsClass.getName() + "$Style");
formattingOptions = formattingOptions.getClass().getConstructor(styleClass, int.class, int.class, int.class, boolean.class, boolean.class, boolean.class).newInstance(
/* style = */ formattingOptionsClass.getMethod("getStyle").invoke(formattingOptions),
Expand All @@ -418,16 +422,26 @@ private Object getCustomFormattingOptions(Class<?> formatterClass) throws Except
/* removeUnusedImports = */ Objects.requireNonNullElse(options.removeUnusedImports, (Boolean) formattingOptionsClass.getMethod("getRemoveUnusedImports").invoke(formattingOptions)),
/* debuggingPrintOpsAfterFormatting = */ (Boolean) formattingOptionsClass.getMethod("getDebuggingPrintOpsAfterFormatting").invoke(formattingOptions),
/* manageTrailingCommas = */ Objects.requireNonNullElse(getManageTrailingCommasFrom(options.trailingCommaManagementStrategy), (Boolean) formattingOptionsClass.getMethod("getManageTrailingCommas").invoke(formattingOptions)));
} else if (BadSemver.version(version) < BadSemver.version(0, 57)) {
formattingOptions = formattingOptions.getClass().getConstructor(int.class, int.class, int.class, boolean.class, boolean.class, boolean.class).newInstance(
/* maxWidth = */ Objects.requireNonNullElse(options.maxWidth, (Integer) formattingOptionsClass.getMethod("getMaxWidth").invoke(formattingOptions)),
/* blockIndent = */ Objects.requireNonNullElse(options.blockIndent, (Integer) formattingOptionsClass.getMethod("getBlockIndent").invoke(formattingOptions)),
/* continuationIndent = */ Objects.requireNonNullElse(options.continuationIndent, (Integer) formattingOptionsClass.getMethod("getContinuationIndent").invoke(formattingOptions)),
/* 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 {
Class<?> styleClass = classLoader.loadClass(formattingOptionsClass.getName() + "$Style");
formattingOptions = formattingOptions.getClass().getConstructor(styleClass, int.class, int.class, int.class, boolean.class, boolean.class, TrailingCommaManagementStrategy.class).newInstance(
/* style = */ formattingOptionsClass.getMethod("getStyle").invoke(formattingOptions),
Class<?> trailingCommaManagementStrategyClass = getTrailingCommaManagementStrategyClazz();
Object trailingCommaManagementStrategy = options.trailingCommaManagementStrategy == null
? formattingOptionsClass.getMethod("getTrailingCommaManagementStrategy").invoke(formattingOptions)
: Enum.valueOf((Class<? extends Enum>) trailingCommaManagementStrategyClass, options.trailingCommaManagementStrategy.name());
formattingOptions = formattingOptions.getClass().getConstructor(int.class, int.class, int.class, trailingCommaManagementStrategyClass, boolean.class, boolean.class).newInstance(
/* maxWidth = */ Objects.requireNonNullElse(options.maxWidth, (Integer) formattingOptionsClass.getMethod("getMaxWidth").invoke(formattingOptions)),
/* blockIndent = */ Objects.requireNonNullElse(options.blockIndent, (Integer) formattingOptionsClass.getMethod("getBlockIndent").invoke(formattingOptions)),
/* continuationIndent = */ Objects.requireNonNullElse(options.continuationIndent, (Integer) formattingOptionsClass.getMethod("getContinuationIndent").invoke(formattingOptions)),
/* trailingCommaManagementStrategy = */ trailingCommaManagementStrategy,
/* removeUnusedImports = */ Objects.requireNonNullElse(options.removeUnusedImports, (Boolean) formattingOptionsClass.getMethod("getRemoveUnusedImports").invoke(formattingOptions)),
/* debuggingPrintOpsAfterFormatting = */ (Boolean) formattingOptionsClass.getMethod("getDebuggingPrintOpsAfterFormatting").invoke(formattingOptions),
/* trailingCommaManagementStrategy */ Objects.requireNonNullElse(options.trailingCommaManagementStrategy, (TrailingCommaManagementStrategy) formattingOptionsClass.getMethod("getTrailingCommaManagementStrategy").invoke(formattingOptions)));
/* debuggingPrintOpsAfterFormatting = */ (Boolean) formattingOptionsClass.getMethod("getDebuggingPrintOpsAfterFormatting").invoke(formattingOptions));
}
}

Expand Down Expand Up @@ -476,6 +490,10 @@ private Class<?> getFormattingOptionsClazz() throws Exception {
return formattingOptionsClazz;
}

private Class<?> getTrailingCommaManagementStrategyClazz() throws Exception {
return classLoader.loadClass(PACKAGE + ".format.TrailingCommaManagementStrategy");
}

private @Nullable Boolean getManageTrailingCommasFrom(
@Nullable TrailingCommaManagementStrategy trailingCommaManagementStrategy
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2025 DiffPlug
* Copyright 2016-2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -40,6 +40,44 @@ void behaviorWithOptions() {
StepHarness.forStep(step).testResource("kotlin/ktfmt/basic.dirty", "kotlin/ktfmt/basic.clean");
}

@Test
void behaviorWithOptions_0_61() {
KtfmtStep.KtfmtFormattingOptions options = new KtfmtStep.KtfmtFormattingOptions();
options.setMaxWidth(100);
FormatterStep step = KtfmtStep.create("0.61", TestProvisioner.mavenCentral(), KtfmtStep.Style.GOOGLE, options);
StepHarness.forStep(step).testResource("kotlin/ktfmt/basic.dirty", "kotlin/ktfmt/basic.clean");
}

@Test
void behavior_0_62() throws Exception {
FormatterStep step = KtfmtStep.create("0.62", TestProvisioner.mavenCentral());
StepHarness.forStep(step).testResource("kotlin/ktfmt/basic.dirty", "kotlin/ktfmt/basic.clean");
}

@Test
void behaviorWithOptions_0_62() {
KtfmtStep.KtfmtFormattingOptions options = new KtfmtStep.KtfmtFormattingOptions();
options.setMaxWidth(100);
FormatterStep step = KtfmtStep.create("0.62", 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();
options.setMaxWidth(100);
FormatterStep step = KtfmtStep.create("0.53", TestProvisioner.mavenCentral(), KtfmtStep.Style.GOOGLE, options);
StepHarness.forStep(step).testResource("kotlin/ktfmt/basic.dirty", "kotlin/ktfmt/basic.clean");
}

@Test
void behaviorWithOptions_0_56() {
KtfmtStep.KtfmtFormattingOptions options = new KtfmtStep.KtfmtFormattingOptions();
options.setMaxWidth(100);
FormatterStep step = KtfmtStep.create("0.56", TestProvisioner.mavenCentral(), KtfmtStep.Style.GOOGLE, options);
StepHarness.forStep(step).testResource("kotlin/ktfmt/basic.dirty", "kotlin/ktfmt/basic.clean");
}

@Test
void dropboxStyle_0_16() throws Exception {
KtfmtStep.KtfmtFormattingOptions options = new KtfmtStep.KtfmtFormattingOptions();
Expand Down
Loading