Skip to content

Commit

Permalink
Implemented max lines width changing support for Kotlin ktfmt formatt…
Browse files Browse the repository at this point in the history
…er for Gradle plugin.
  • Loading branch information
DDeg committed Mar 11, 2022
1 parent 32210d0 commit b679ec9
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 31 deletions.
72 changes: 57 additions & 15 deletions lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="https://github.com/facebookincubator/ktfmt">ktfmt</a> as a FormatterStep.
Expand Down Expand Up @@ -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 <code>format</code> method is available in the link below.
*
Expand All @@ -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() {
Expand All @@ -104,16 +114,22 @@ 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.
*/
private final Style style;
/** 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);
}
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public KtfmtConfig ktfmt(String version) {

public class KtfmtConfig {
final String version;
Integer maxWidth;
Style style;

KtfmtConfig(String version) {
Expand All @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
12 changes: 12 additions & 0 deletions testlib/src/main/resources/kotlin/ktfmt/max-width-dropbox.clean
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 12 additions & 0 deletions testlib/src/main/resources/kotlin/ktfmt/max-width.clean
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 12 additions & 0 deletions testlib/src/main/resources/kotlin/ktfmt/max-width.dirty
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit b679ec9

Please sign in to comment.