Skip to content

Commit b679ec9

Browse files
committed
Implemented max lines width changing support for Kotlin ktfmt formatter for Gradle plugin.
1 parent 32210d0 commit b679ec9

File tree

7 files changed

+172
-31
lines changed

7 files changed

+172
-31
lines changed

lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
import java.io.Serializable;
2222
import java.lang.reflect.InvocationTargetException;
2323
import java.lang.reflect.Method;
24+
import java.util.Arrays;
2425
import java.util.Objects;
2526

2627
import com.diffplug.spotless.*;
28+
import javax.annotation.Nullable;
2729

2830
/**
2931
* Wraps up <a href="https://github.com/facebookincubator/ktfmt">ktfmt</a> as a FormatterStep.
@@ -64,6 +66,14 @@ String getSince() {
6466

6567
private static final String DROPBOX_STYLE_METHOD = "dropboxStyle";
6668

69+
private static final String FORMATTING_OPTIONS_METHOD_COPY = "copy";
70+
private static final String FORMATTING_OPTIONS_METHOD_GET_STYLE = "getStyle";
71+
private static final String FORMATTING_OPTIONS_METHOD_GET_MAX_WIDTH = "getMaxWidth";
72+
private static final String FORMATTING_OPTIONS_METHOD_GET_BLOCK_INDENT = "getBlockIndent";
73+
private static final String FORMATTING_OPTIONS_METHOD_GET_CONTINUATION_INDENT = "getContinuationIndent";
74+
private static final String FORMATTING_OPTIONS_METHOD_GET_REMOVE_UNUSED_IMPORTS = "getRemoveUnusedImports";
75+
private static final String FORMATTING_OPTIONS_METHOD_GET_DEBUGGING_PRINT_OPS_AFTER_FORMATTING = "getDebuggingPrintOpsAfterFormatting";
76+
6777
/**
6878
* The <code>format</code> method is available in the link below.
6979
*
@@ -78,16 +88,16 @@ public static FormatterStep create(Provisioner provisioner) {
7888

7989
/** Creates a step which formats everything - code, import order, and unused imports. */
8090
public static FormatterStep create(String version, Provisioner provisioner) {
81-
return create(version, provisioner, DEFAULT);
91+
return create(version, provisioner, null, DEFAULT);
8292
}
8393

8494
/** Creates a step which formats everything - code, import order, and unused imports. */
85-
public static FormatterStep create(String version, Provisioner provisioner, Style style) {
95+
public static FormatterStep create(String version, Provisioner provisioner, @Nullable Integer maxWidth, Style style) {
8696
Objects.requireNonNull(version, "version");
8797
Objects.requireNonNull(provisioner, "provisioner");
8898
Objects.requireNonNull(style, "style");
8999
return FormatterStep.createLazy(
90-
NAME, () -> new State(version, provisioner, style), State::createFormat);
100+
NAME, () -> new State(version, provisioner, maxWidth, style), State::createFormat);
91101
}
92102

93103
public static String defaultVersion() {
@@ -104,16 +114,22 @@ static final class State implements Serializable {
104114
private final String version;
105115

106116
private final String pkg;
117+
/**
118+
* Option that allows change line width before breaking
119+
*/
120+
@Nullable
121+
private final Integer maxWidth;
107122
/**
108123
* Option that allows to apply formatting options to perform a 4 spaces block and continuation indent.
109124
*/
110125
private final Style style;
111126
/** The jar that contains the formatter. */
112127
final JarState jarState;
113128

114-
State(String version, Provisioner provisioner, Style style) throws IOException {
129+
State(String version, Provisioner provisioner, @Nullable Integer maxWidth, Style style) throws IOException {
115130
this.version = version;
116131
this.pkg = PACKAGE;
132+
this.maxWidth = maxWidth;
117133
this.style = style;
118134
this.jarState = JarState.from(MAVEN_COORDINATE + version, provisioner);
119135
}
@@ -122,29 +138,55 @@ FormatterFunc createFormat() throws Exception {
122138
ClassLoader classLoader = jarState.getClassLoader();
123139
return input -> {
124140
try {
125-
if (style == DEFAULT) {
126-
Method formatterMethod = getFormatterClazz(classLoader).getMethod(FORMATTER_METHOD, String.class);
127-
return (String) formatterMethod.invoke(getFormatterClazz(classLoader), input);
128-
} else {
129-
Method formatterMethod = getFormatterClazz(classLoader).getMethod(FORMATTER_METHOD, getFormattingOptionsClazz(classLoader),
130-
String.class);
131-
Object formattingOptions = getCustomFormattingOptions(classLoader, style);
132-
return (String) formatterMethod.invoke(getFormatterClazz(classLoader), formattingOptions, input);
133-
}
141+
Method formatterMethod = getFormatterClazz(classLoader).getMethod(FORMATTER_METHOD, getFormattingOptionsClazz(classLoader),
142+
String.class);
143+
Object formattingOptions = getCustomFormattingOptions(classLoader, maxWidth, style);
144+
return (String) formatterMethod.invoke(getFormatterClazz(classLoader), formattingOptions, input);
134145
} catch (InvocationTargetException e) {
135146
throw ThrowingEx.unwrapCause(e);
136147
}
137148
};
138149
}
139150

140-
private Object getCustomFormattingOptions(ClassLoader classLoader, Style style) throws Exception {
151+
private Object getCustomFormattingOptions(ClassLoader classLoader, @Nullable Integer maxWidth, Style style) throws Exception {
141152
if (BadSemver.version(version) < BadSemver.version(style.since)) {
142153
throw new IllegalStateException(String.format("The style %s is available from version %s (current version: %s)", style.name(), style.since, version));
143154
}
144155

145156
try {
146157
// ktfmt v0.19 and later
147-
return getFormatterClazz(classLoader).getField(style.getFormat()).get(null);
158+
Object formattingOptionVariable;
159+
if (style == DEFAULT) {
160+
formattingOptionVariable = getFormattingOptionsClazz(classLoader).getDeclaredConstructor().newInstance();
161+
} else {
162+
formattingOptionVariable = getFormatterClazz(classLoader).getField(style.getFormat()).get(null);
163+
}
164+
165+
if (maxWidth != null) {
166+
if (BadSemver.version(version) < BadSemver.version(0, 31)) {
167+
throw new IllegalStateException("Max width configuration supported only for ktfmt 0.31 and later");
168+
}
169+
170+
Class<?> formattingOptionsClass = getFormattingOptionsClazz(classLoader);
171+
Object styleValue = formattingOptionsClass.getMethod(FORMATTING_OPTIONS_METHOD_GET_STYLE).invoke(formattingOptionVariable);
172+
Object maxWidthValue = formattingOptionsClass.getMethod(FORMATTING_OPTIONS_METHOD_GET_MAX_WIDTH).invoke(formattingOptionVariable);
173+
Object blockIndentValue = formattingOptionsClass.getMethod(FORMATTING_OPTIONS_METHOD_GET_BLOCK_INDENT).invoke(formattingOptionVariable);
174+
Object continuationIndentValue = formattingOptionsClass.getMethod(FORMATTING_OPTIONS_METHOD_GET_CONTINUATION_INDENT).invoke(formattingOptionVariable);
175+
Object removeUnusedImportsValue = formattingOptionsClass.getMethod(FORMATTING_OPTIONS_METHOD_GET_REMOVE_UNUSED_IMPORTS).invoke(formattingOptionVariable);
176+
Object debuggingPrintOpsAfterFormattingValue = formattingOptionsClass.getMethod(FORMATTING_OPTIONS_METHOD_GET_DEBUGGING_PRINT_OPS_AFTER_FORMATTING).invoke(formattingOptionVariable);
177+
178+
Method copyFormattingOption = Arrays.stream(formattingOptionsClass.getDeclaredMethods())
179+
.filter(method -> FORMATTING_OPTIONS_METHOD_COPY.equals(method.getName())).findFirst().get();
180+
formattingOptionVariable = copyFormattingOption.invoke(formattingOptionVariable,
181+
styleValue,
182+
maxWidth > 0 ? maxWidth : maxWidthValue,
183+
blockIndentValue,
184+
continuationIndentValue,
185+
removeUnusedImportsValue,
186+
debuggingPrintOpsAfterFormattingValue);
187+
}
188+
189+
return formattingOptionVariable;
148190
} catch (NoSuchFieldException ignored) {}
149191

150192
// fallback to old, pre-0.19 ktfmt interface.

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public KtfmtConfig ktfmt(String version) {
103103

104104
public class KtfmtConfig {
105105
final String version;
106+
Integer maxWidth;
106107
Style style;
107108

108109
KtfmtConfig(String version) {
@@ -111,25 +112,35 @@ public class KtfmtConfig {
111112
addStep(createStep());
112113
}
113114

114-
public void dropboxStyle() {
115-
style(Style.DROPBOX);
115+
public KtfmtConfig maxWidth(int maxWidth) {
116+
if (maxWidth <= 0) {
117+
throw new IllegalArgumentException("Passed maxWidth parameter must be positive value");
118+
}
119+
this.maxWidth = maxWidth;
120+
replaceStep(createStep());
121+
return this;
116122
}
117123

118-
public void googleStyle() {
119-
style(Style.GOOGLE);
124+
public KtfmtConfig dropboxStyle() {
125+
return style(Style.DROPBOX);
120126
}
121127

122-
public void kotlinlangStyle() {
123-
style(Style.KOTLINLANG);
128+
public KtfmtConfig googleStyle() {
129+
return style(Style.GOOGLE);
124130
}
125131

126-
public void style(Style style) {
132+
public KtfmtConfig kotlinlangStyle() {
133+
return style(Style.KOTLINLANG);
134+
}
135+
136+
public KtfmtConfig style(Style style) {
127137
this.style = style;
128138
replaceStep(createStep());
139+
return this;
129140
}
130141

131142
private FormatterStep createStep() {
132-
return KtfmtStep.create(version, provisioner(), style);
143+
return KtfmtStep.create(version, provisioner(), maxWidth, style);
133144
}
134145
}
135146

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,33 +89,45 @@ public KtfmtConfig ktfmt(String version) {
8989

9090
public class KtfmtConfig {
9191
final String version;
92+
Integer maxWidth;
9293
Style style;
9394

9495
KtfmtConfig(String version) {
9596
this.version = Objects.requireNonNull(version);
97+
this.maxWidth = null;
9698
this.style = Style.DEFAULT;
9799
addStep(createStep());
98100
}
99101

100-
public void style(Style style) {
102+
public KtfmtConfig maxWidth(int maxWidth) {
103+
if (maxWidth <= 0) {
104+
throw new IllegalArgumentException("Passed maxWidth parameter must be positive value");
105+
}
106+
this.maxWidth = maxWidth;
107+
replaceStep(createStep());
108+
return this;
109+
}
110+
111+
public KtfmtConfig style(Style style) {
101112
this.style = style;
102113
replaceStep(createStep());
114+
return this;
103115
}
104116

105-
public void dropboxStyle() {
106-
style(Style.DROPBOX);
117+
public KtfmtConfig dropboxStyle() {
118+
return style(Style.DROPBOX);
107119
}
108120

109-
public void googleStyle() {
110-
style(Style.GOOGLE);
121+
public KtfmtConfig googleStyle() {
122+
return style(Style.GOOGLE);
111123
}
112124

113-
public void kotlinlangStyle() {
114-
style(Style.KOTLINLANG);
125+
public KtfmtConfig kotlinlangStyle() {
126+
return style(Style.KOTLINLANG);
115127
}
116128

117129
private FormatterStep createStep() {
118-
return KtfmtStep.create(version, provisioner(), style);
130+
return KtfmtStep.create(version, provisioner(), maxWidth, style);
119131
}
120132
}
121133

plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,4 +269,44 @@ void testWithNonStandardYearSeparatorKtfmt() throws IOException {
269269
matcher.startsWith("// License Header 2012, 2014");
270270
});
271271
}
272+
273+
@Test
274+
@EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+.
275+
void testWithCustomMaxWidthDefaultStyleKtfmt() throws IOException {
276+
setFile("build.gradle").toLines(
277+
"plugins {",
278+
" id 'org.jetbrains.kotlin.jvm' version '1.5.31'",
279+
" id 'com.diffplug.spotless'",
280+
"}",
281+
"repositories { mavenCentral() }",
282+
"spotless {",
283+
" kotlin {",
284+
" ktfmt().maxWidth(120)",
285+
" }",
286+
"}");
287+
288+
setFile("src/main/kotlin/max-width.kt").toResource("kotlin/ktfmt/max-width.dirty");
289+
gradleRunner().withArguments("spotlessApply").build();
290+
assertFile("src/main/kotlin/max-width.kt").sameAsResource("kotlin/ktfmt/max-width.clean");
291+
}
292+
293+
@Test
294+
@EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+.
295+
void testWithCustomMaxWidthDropboxStyleKtfmt() throws IOException {
296+
setFile("build.gradle").toLines(
297+
"plugins {",
298+
" id 'org.jetbrains.kotlin.jvm' version '1.5.31'",
299+
" id 'com.diffplug.spotless'",
300+
"}",
301+
"repositories { mavenCentral() }",
302+
"spotless {",
303+
" kotlin {",
304+
" ktfmt().dropboxStyle().maxWidth(120)",
305+
" }",
306+
"}");
307+
308+
setFile("src/main/kotlin/max-width.kt").toResource("kotlin/ktfmt/max-width.dirty");
309+
gradleRunner().withArguments("spotlessApply").build();
310+
assertFile("src/main/kotlin/max-width.kt").sameAsResource("kotlin/ktfmt/max-width-dropbox.clean");
311+
}
272312
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import a.*
2+
import a.b
3+
import a.b.c.*
4+
import kotlinx.android.synthetic.main.layout_name.*
5+
6+
fun main() {}
7+
8+
fun foo(param1: Any, param2: Any, param3: Any, param4: Any, param5: Any, param6: Any, param7: Any, param8: Any) {
9+
a()
10+
functionNameWithAlmost120SymbolsToValidateThatKtfmtNotBreakLineAtDefault100(param1, param2, param3, param4, param5)
11+
return b
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import a.*
2+
import a.b
3+
import a.b.c.*
4+
import kotlinx.android.synthetic.main.layout_name.*
5+
6+
fun main() {}
7+
8+
fun foo(param1: Any, param2: Any, param3: Any, param4: Any, param5: Any, param6: Any, param7: Any, param8: Any) {
9+
a()
10+
functionNameWithAlmost120SymbolsToValidateThatKtfmtNotBreakLineAtDefault100(param1, param2, param3, param4, param5)
11+
return b
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import a.*
2+
3+
import kotlinx.android.synthetic.main.layout_name.*
4+
5+
import a.b.c.*
6+
import a.b
7+
8+
fun main() {}
9+
fun foo(param1: Any, param2: Any, param3: Any, param4: Any, param5: Any, param6: Any, param7: Any, param8: Any) {
10+
a(); functionNameWithAlmost120SymbolsToValidateThatKtfmtNotBreakLineAtDefault100(param1, param2, param3, param4, param5)
11+
return b
12+
}

0 commit comments

Comments
 (0)