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 @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
};
}
}
58 changes: 47 additions & 11 deletions lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,18 @@
import com.diffplug.spotless.ThrowingEx;

/**
* Wraps up <a href="https://github.com/facebook/ktfmt">ktfmt</a> as a FormatterStep.
* Wraps up <a href="https://github.com/Kotlin/ktfmt">ktfmt</a> 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;
/**
Expand All @@ -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 <a href="https://github.com/facebook/ktfmt/blob/v0.51/core/src/main/java/com/facebook/ktfmt/format/Formatter.kt#L45-L68">ktfmt source</a>
* @see <a href="https://github.com/Kotlin/ktfmt/blob/v0.51/core/src/main/java/com/Kotlin/ktfmt/format/Formatter.kt#L45-L68">ktfmt source</a>
*/
public enum Style {
// @formatter:off
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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 <code>format</code> method is available in the link below.
*
* @see <a href="https://github.com/facebook/ktfmt/blob/v0.51/core/src/main/java/com/facebook/ktfmt/format/Formatter.kt#L78-L94">ktfmt source</a>
* @see <a href="https://github.com/Kotlin/ktfmt/blob/v0.51/core/src/main/java/com/Kotlin/ktfmt/format/Formatter.kt#L78-L94">ktfmt source</a>
*/
static final String FORMATTER_METHOD = "format";

Expand Down Expand Up @@ -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)
Expand All @@ -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);
}
}

Expand Down
2 changes: 2 additions & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ public KtlintConfig ktlint(String version) throws IOException {
return new KtlintConfig(version, Collections.emptyMap(), Collections.emptyList());
}

/** Uses the <a href="https://github.com/facebook/ktfmt">ktfmt</a> jar to format source code. */
/** Uses the <a href="https://github.com/Kotlin/ktfmt">ktfmt</a> jar to format source code. */
public KtfmtConfig ktfmt() {
return ktfmt(KtfmtStep.defaultVersion());
}

/**
* Uses the given version of <a href="https://github.com/facebook/ktfmt">ktfmt</a> and applies the dropbox style
* Uses the given version of <a href="https://github.com/Kotlin/ktfmt">ktfmt</a> and applies the dropbox style
* option to format source code.
*/
public KtfmtConfig ktfmt(String version) {
Expand Down
2 changes: 2 additions & 0 deletions plugin-maven/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion plugin-maven/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<ktfmt>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading