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 9f3b62dadb..2fcdc566cb 100644
--- a/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java
+++ b/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java
@@ -21,9 +21,11 @@
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.Arrays;
import java.util.Objects;
import com.diffplug.spotless.*;
+import javax.annotation.Nullable;
/**
* Wraps up ktfmt as a FormatterStep.
@@ -64,6 +66,14 @@ String getSince() {
private static final String DROPBOX_STYLE_METHOD = "dropboxStyle";
+ private static final String FORMATTING_OPTIONS_METHOD_COPY = "copy";
+ private static final String FORMATTING_OPTIONS_METHOD_GET_STYLE = "getStyle";
+ private static final String FORMATTING_OPTIONS_METHOD_GET_MAX_WIDTH = "getMaxWidth";
+ private static final String FORMATTING_OPTIONS_METHOD_GET_BLOCK_INDENT = "getBlockIndent";
+ private static final String FORMATTING_OPTIONS_METHOD_GET_CONTINUATION_INDENT = "getContinuationIndent";
+ private static final String FORMATTING_OPTIONS_METHOD_GET_REMOVE_UNUSED_IMPORTS = "getRemoveUnusedImports";
+ private static final String FORMATTING_OPTIONS_METHOD_GET_DEBUGGING_PRINT_OPS_AFTER_FORMATTING = "getDebuggingPrintOpsAfterFormatting";
+
/**
* The format
method is available in the link below.
*
@@ -78,16 +88,16 @@ public static FormatterStep create(Provisioner provisioner) {
/** Creates a step which formats everything - code, import order, and unused imports. */
public static FormatterStep create(String version, Provisioner provisioner) {
- return create(version, provisioner, DEFAULT);
+ return create(version, provisioner, null, DEFAULT);
}
/** Creates a step which formats everything - code, import order, and unused imports. */
- public static FormatterStep create(String version, Provisioner provisioner, Style style) {
+ public static FormatterStep create(String version, Provisioner provisioner, @Nullable Integer maxWidth, Style style) {
Objects.requireNonNull(version, "version");
Objects.requireNonNull(provisioner, "provisioner");
Objects.requireNonNull(style, "style");
return FormatterStep.createLazy(
- NAME, () -> new State(version, provisioner, style), State::createFormat);
+ NAME, () -> new State(version, provisioner, maxWidth, style), State::createFormat);
}
public static String defaultVersion() {
@@ -104,6 +114,11 @@ static final class State implements Serializable {
private final String version;
private final String pkg;
+ /**
+ * Option that allows change line width before breaking
+ */
+ @Nullable
+ private final Integer maxWidth;
/**
* Option that allows to apply formatting options to perform a 4 spaces block and continuation indent.
*/
@@ -111,9 +126,10 @@ static final class State implements Serializable {
/** The jar that contains the formatter. */
final JarState jarState;
- State(String version, Provisioner provisioner, Style style) throws IOException {
+ State(String version, Provisioner provisioner, @Nullable Integer maxWidth, Style style) throws IOException {
this.version = version;
this.pkg = PACKAGE;
+ this.maxWidth = maxWidth;
this.style = style;
this.jarState = JarState.from(MAVEN_COORDINATE + version, provisioner);
}
@@ -122,29 +138,55 @@ FormatterFunc createFormat() throws Exception {
ClassLoader classLoader = jarState.getClassLoader();
return input -> {
try {
- if (style == DEFAULT) {
- Method formatterMethod = getFormatterClazz(classLoader).getMethod(FORMATTER_METHOD, String.class);
- return (String) formatterMethod.invoke(getFormatterClazz(classLoader), input);
- } else {
- Method formatterMethod = getFormatterClazz(classLoader).getMethod(FORMATTER_METHOD, getFormattingOptionsClazz(classLoader),
- String.class);
- Object formattingOptions = getCustomFormattingOptions(classLoader, style);
- return (String) formatterMethod.invoke(getFormatterClazz(classLoader), formattingOptions, input);
- }
+ Method formatterMethod = getFormatterClazz(classLoader).getMethod(FORMATTER_METHOD, getFormattingOptionsClazz(classLoader),
+ String.class);
+ Object formattingOptions = getCustomFormattingOptions(classLoader, maxWidth, style);
+ return (String) formatterMethod.invoke(getFormatterClazz(classLoader), formattingOptions, input);
} catch (InvocationTargetException e) {
throw ThrowingEx.unwrapCause(e);
}
};
}
- private Object getCustomFormattingOptions(ClassLoader classLoader, Style style) throws Exception {
+ private Object getCustomFormattingOptions(ClassLoader classLoader, @Nullable Integer maxWidth, Style style) throws Exception {
if (BadSemver.version(version) < BadSemver.version(style.since)) {
throw new IllegalStateException(String.format("The style %s is available from version %s (current version: %s)", style.name(), style.since, version));
}
try {
// ktfmt v0.19 and later
- return getFormatterClazz(classLoader).getField(style.getFormat()).get(null);
+ Object formattingOptionVariable;
+ if (style == DEFAULT) {
+ formattingOptionVariable = getFormattingOptionsClazz(classLoader).getDeclaredConstructor().newInstance();
+ } else {
+ formattingOptionVariable = getFormatterClazz(classLoader).getField(style.getFormat()).get(null);
+ }
+
+ if (maxWidth != null) {
+ if (BadSemver.version(version) < BadSemver.version(0, 31)) {
+ throw new IllegalStateException("Max width configuration supported only for ktfmt 0.31 and later");
+ }
+
+ Class> formattingOptionsClass = getFormattingOptionsClazz(classLoader);
+ Object styleValue = formattingOptionsClass.getMethod(FORMATTING_OPTIONS_METHOD_GET_STYLE).invoke(formattingOptionVariable);
+ Object maxWidthValue = formattingOptionsClass.getMethod(FORMATTING_OPTIONS_METHOD_GET_MAX_WIDTH).invoke(formattingOptionVariable);
+ Object blockIndentValue = formattingOptionsClass.getMethod(FORMATTING_OPTIONS_METHOD_GET_BLOCK_INDENT).invoke(formattingOptionVariable);
+ Object continuationIndentValue = formattingOptionsClass.getMethod(FORMATTING_OPTIONS_METHOD_GET_CONTINUATION_INDENT).invoke(formattingOptionVariable);
+ Object removeUnusedImportsValue = formattingOptionsClass.getMethod(FORMATTING_OPTIONS_METHOD_GET_REMOVE_UNUSED_IMPORTS).invoke(formattingOptionVariable);
+ Object debuggingPrintOpsAfterFormattingValue = formattingOptionsClass.getMethod(FORMATTING_OPTIONS_METHOD_GET_DEBUGGING_PRINT_OPS_AFTER_FORMATTING).invoke(formattingOptionVariable);
+
+ Method copyFormattingOption = Arrays.stream(formattingOptionsClass.getDeclaredMethods())
+ .filter(method -> FORMATTING_OPTIONS_METHOD_COPY.equals(method.getName())).findFirst().get();
+ formattingOptionVariable = copyFormattingOption.invoke(formattingOptionVariable,
+ styleValue,
+ maxWidth > 0 ? maxWidth : maxWidthValue,
+ blockIndentValue,
+ continuationIndentValue,
+ removeUnusedImportsValue,
+ debuggingPrintOpsAfterFormattingValue);
+ }
+
+ return formattingOptionVariable;
} catch (NoSuchFieldException ignored) {}
// fallback to old, pre-0.19 ktfmt interface.
diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java
index d88b4aac7f..d6ba28a3c4 100644
--- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java
+++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java
@@ -103,6 +103,7 @@ public KtfmtConfig ktfmt(String version) {
public class KtfmtConfig {
final String version;
+ Integer maxWidth;
Style style;
KtfmtConfig(String version) {
@@ -111,25 +112,35 @@ public class KtfmtConfig {
addStep(createStep());
}
- public void dropboxStyle() {
- style(Style.DROPBOX);
+ public KtfmtConfig maxWidth(int maxWidth) {
+ if (maxWidth <= 0) {
+ throw new IllegalArgumentException("Passed maxWidth parameter must be positive value");
+ }
+ this.maxWidth = maxWidth;
+ replaceStep(createStep());
+ return this;
}
- public void googleStyle() {
- style(Style.GOOGLE);
+ public KtfmtConfig dropboxStyle() {
+ return style(Style.DROPBOX);
}
- public void kotlinlangStyle() {
- style(Style.KOTLINLANG);
+ public KtfmtConfig googleStyle() {
+ return style(Style.GOOGLE);
}
- public void style(Style style) {
+ public KtfmtConfig kotlinlangStyle() {
+ return style(Style.KOTLINLANG);
+ }
+
+ public KtfmtConfig style(Style style) {
this.style = style;
replaceStep(createStep());
+ return this;
}
private FormatterStep createStep() {
- return KtfmtStep.create(version, provisioner(), style);
+ return KtfmtStep.create(version, provisioner(), maxWidth, style);
}
}
diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java
index 5ea3c29594..4429f541c0 100644
--- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java
+++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java
@@ -89,33 +89,45 @@ public KtfmtConfig ktfmt(String version) {
public class KtfmtConfig {
final String version;
+ Integer maxWidth;
Style style;
KtfmtConfig(String version) {
this.version = Objects.requireNonNull(version);
+ this.maxWidth = null;
this.style = Style.DEFAULT;
addStep(createStep());
}
- public void style(Style style) {
+ public KtfmtConfig maxWidth(int maxWidth) {
+ if (maxWidth <= 0) {
+ throw new IllegalArgumentException("Passed maxWidth parameter must be positive value");
+ }
+ this.maxWidth = maxWidth;
+ replaceStep(createStep());
+ return this;
+ }
+
+ public KtfmtConfig style(Style style) {
this.style = style;
replaceStep(createStep());
+ return this;
}
- public void dropboxStyle() {
- style(Style.DROPBOX);
+ public KtfmtConfig dropboxStyle() {
+ return style(Style.DROPBOX);
}
- public void googleStyle() {
- style(Style.GOOGLE);
+ public KtfmtConfig googleStyle() {
+ return style(Style.GOOGLE);
}
- public void kotlinlangStyle() {
- style(Style.KOTLINLANG);
+ public KtfmtConfig kotlinlangStyle() {
+ return style(Style.KOTLINLANG);
}
private FormatterStep createStep() {
- return KtfmtStep.create(version, provisioner(), style);
+ return KtfmtStep.create(version, provisioner(), maxWidth, style);
}
}
diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java
index f3d8ad442d..0feb110666 100644
--- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java
+++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java
@@ -269,4 +269,44 @@ void testWithNonStandardYearSeparatorKtfmt() throws IOException {
matcher.startsWith("// License Header 2012, 2014");
});
}
+
+ @Test
+ @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+.
+ void testWithCustomMaxWidthDefaultStyleKtfmt() throws IOException {
+ setFile("build.gradle").toLines(
+ "plugins {",
+ " id 'org.jetbrains.kotlin.jvm' version '1.5.31'",
+ " id 'com.diffplug.spotless'",
+ "}",
+ "repositories { mavenCentral() }",
+ "spotless {",
+ " kotlin {",
+ " ktfmt().maxWidth(120)",
+ " }",
+ "}");
+
+ setFile("src/main/kotlin/max-width.kt").toResource("kotlin/ktfmt/max-width.dirty");
+ gradleRunner().withArguments("spotlessApply").build();
+ assertFile("src/main/kotlin/max-width.kt").sameAsResource("kotlin/ktfmt/max-width.clean");
+ }
+
+ @Test
+ @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+.
+ void testWithCustomMaxWidthDropboxStyleKtfmt() throws IOException {
+ setFile("build.gradle").toLines(
+ "plugins {",
+ " id 'org.jetbrains.kotlin.jvm' version '1.5.31'",
+ " id 'com.diffplug.spotless'",
+ "}",
+ "repositories { mavenCentral() }",
+ "spotless {",
+ " kotlin {",
+ " ktfmt().dropboxStyle().maxWidth(120)",
+ " }",
+ "}");
+
+ setFile("src/main/kotlin/max-width.kt").toResource("kotlin/ktfmt/max-width.dirty");
+ gradleRunner().withArguments("spotlessApply").build();
+ assertFile("src/main/kotlin/max-width.kt").sameAsResource("kotlin/ktfmt/max-width-dropbox.clean");
+ }
}
diff --git a/testlib/src/main/resources/kotlin/ktfmt/max-width-dropbox.clean b/testlib/src/main/resources/kotlin/ktfmt/max-width-dropbox.clean
new file mode 100644
index 0000000000..eb0715b1ec
--- /dev/null
+++ b/testlib/src/main/resources/kotlin/ktfmt/max-width-dropbox.clean
@@ -0,0 +1,12 @@
+import a.*
+import a.b
+import a.b.c.*
+import kotlinx.android.synthetic.main.layout_name.*
+
+fun main() {}
+
+fun foo(param1: Any, param2: Any, param3: Any, param4: Any, param5: Any, param6: Any, param7: Any, param8: Any) {
+ a()
+ functionNameWithAlmost120SymbolsToValidateThatKtfmtNotBreakLineAtDefault100(param1, param2, param3, param4, param5)
+ return b
+}
\ No newline at end of file
diff --git a/testlib/src/main/resources/kotlin/ktfmt/max-width.clean b/testlib/src/main/resources/kotlin/ktfmt/max-width.clean
new file mode 100644
index 0000000000..4592181044
--- /dev/null
+++ b/testlib/src/main/resources/kotlin/ktfmt/max-width.clean
@@ -0,0 +1,12 @@
+import a.*
+import a.b
+import a.b.c.*
+import kotlinx.android.synthetic.main.layout_name.*
+
+fun main() {}
+
+fun foo(param1: Any, param2: Any, param3: Any, param4: Any, param5: Any, param6: Any, param7: Any, param8: Any) {
+ a()
+ functionNameWithAlmost120SymbolsToValidateThatKtfmtNotBreakLineAtDefault100(param1, param2, param3, param4, param5)
+ return b
+}
diff --git a/testlib/src/main/resources/kotlin/ktfmt/max-width.dirty b/testlib/src/main/resources/kotlin/ktfmt/max-width.dirty
new file mode 100644
index 0000000000..ef9504e1c3
--- /dev/null
+++ b/testlib/src/main/resources/kotlin/ktfmt/max-width.dirty
@@ -0,0 +1,12 @@
+import a.*
+
+import kotlinx.android.synthetic.main.layout_name.*
+
+import a.b.c.*
+import a.b
+
+fun main() {}
+fun foo(param1: Any, param2: Any, param3: Any, param4: Any, param5: Any, param6: Any, param7: Any, param8: Any) {
+ a(); functionNameWithAlmost120SymbolsToValidateThatKtfmtNotBreakLineAtDefault100(param1, param2, param3, param4, param5)
+ return b
+}