Skip to content
Open
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 @@ -12,6 +12,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
## [Unreleased]
### Added
- Add a `cleanup` option to `GoogleJavaFormatStep` to disable google-java-format's non-whitespace cleanups. ([#3157](https://github.com/diffplug/spotless/pull/3157))
- Add a `maxLineLength` option to `GoogleJavaFormatStep`. ([#3151](https://github.com/diffplug/spotless/issues/3151))
Overrides the 100-column limit prescribed by the GOOGLE and AOSP styles. Requires `google-java-format` `2.0.0` or later, the bug-fixing major release which stabilized the configurable `JavaFormatterOptions.Style` introduced experimentally in `1.37.0` (no `1.38.0` was ever published).
### Changes
- Bump default `google-java-format` version `1.36.1` -> `2.0.0`. ([#3157](https://github.com/diffplug/spotless/pull/3157))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.diffplug.spotless.glue.java;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;

Expand Down Expand Up @@ -47,13 +48,17 @@ public class GoogleJavaFormatFormatterFunc implements FormatterFunc {

private final boolean reorderImports;

public GoogleJavaFormatFormatterFunc(@Nonnull String version, @Nonnull String style, boolean reflowStrings, boolean reorderImports, boolean formatJavadoc, boolean cleanup) {
public GoogleJavaFormatFormatterFunc(@Nonnull String version, @Nonnull String style, boolean reflowStrings, boolean reorderImports, boolean formatJavadoc, boolean cleanup, int maxLineLength) {
this.version = Objects.requireNonNull(version);
this.formatterStyle = Style.valueOf(Objects.requireNonNull(style));
this.reflowStrings = reflowStrings;
this.reorderImports = reorderImports;

JavaFormatterOptions.Builder builder = JavaFormatterOptions.builder().style(formatterStyle);
Style effectiveStyle = formatterStyle;
if (maxLineLength > 0) {
effectiveStyle = withMaxLineLength(formatterStyle, maxLineLength, version);
}
JavaFormatterOptions.Builder builder = JavaFormatterOptions.builder().style(effectiveStyle);
if (!formatJavadoc) {
builder = builder.formatJavadoc(false);
}
Expand All @@ -63,6 +68,33 @@ public GoogleJavaFormatFormatterFunc(@Nonnull String version, @Nonnull String st
this.formatter = new Formatter(builder.build());
}

/**
* Sets the column limit on the style's builder. That builder was introduced experimentally in
* google-java-format 1.37.0 and stabilized in 2.0.0, the bug-fixing major release (no 1.38.0
* was ever published, see google/google-java-format#1477), while this glue code is compiled
* against 1.28.0 so that it keeps working on older versions, hence the reflection
* (https://github.com/diffplug/spotless/issues/3151).
* Methods are looked up on the public {@code Style$Builder} type rather than on the runtime
* class of the builder, because the runtime class generated by AutoBuilder is package-private.
*/
@Nonnull
private static Style withMaxLineLength(@Nonnull Style style, int maxLineLength, @Nonnull String version) {
try {
Method createBuilder = Style.class.getMethod("builder");
Class<?> builderType = createBuilder.getReturnType();
Object builder = createBuilder.invoke(null);
builder = builderType.getMethod(style == Style.AOSP ? "aosp" : "google").invoke(builder);
builder = builderType.getMethod("maxLineLength", int.class).invoke(builder, maxLineLength);
return (Style) builderType.getMethod("build").invoke(builder);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(
"maxLineLength requires google-java-format 2.0.0 or later (you are using " + version + ")", e);
} catch (IllegalAccessException | InvocationTargetException e) {
Throwable cause = e instanceof InvocationTargetException ? e.getCause() : e;
throw new IllegalArgumentException("Failed to apply maxLineLength " + maxLineLength, cause);
}
}

// JavaFormatterOptions.Builder.cleanup(boolean) was added in google-java-format 2.0.0, and this
// glue is compiled against an older version.
private static JavaFormatterOptions.Builder disableCleanup(JavaFormatterOptions.Builder builder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.io.Serial;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Objects;

import com.diffplug.spotless.FormatterFunc;
Expand All @@ -35,6 +36,16 @@ public final class GoogleJavaFormatStep implements Serializable {
private static final boolean DEFAULT_REORDER_IMPORTS = false;
private static final boolean DEFAULT_FORMAT_JAVADOC = true;
private static final boolean DEFAULT_CLEANUP = true;
/** 0 means "not set", i.e. use whatever column limit the formatter's style prescribes. */
private static final int DEFAULT_MAX_LINE_LENGTH = 0;
/**
* The minimum version for the {@code maxLineLength} option: google-java-format {@code 2.0.0},
* the bug-fixing major release which stabilized the configurable {@code JavaFormatterOptions.Style}
* introduced experimentally in 1.37.0 (no 1.38.0 was ever published), see
* [google/google-java-format#1477](https://github.com/google/google-java-format/pull/1477) and
* [#3151](https://github.com/diffplug/spotless/issues/3151).
*/
static final String MIN_VERSION_FOR_MAX_LINE_LENGTH = "2.0.0";
private static final String NAME = "google-java-format";
private static final String MAVEN_COORDINATE = "com.google.googlejavaformat:google-java-format";

Expand All @@ -46,21 +57,24 @@ public final class GoogleJavaFormatStep implements Serializable {
private final boolean reorderImports;
private final boolean formatJavadoc;
private final boolean cleanup;
private final int maxLineLength;

private GoogleJavaFormatStep(JarState.Promised jarState,
String version,
String style,
boolean reflowLongStrings,
boolean reorderImports,
boolean formatJavadoc,
boolean cleanup) {
boolean cleanup,
int maxLineLength) {
this.jarState = jarState;
this.version = version;
this.style = style;
this.reflowLongStrings = reflowLongStrings;
this.reorderImports = reorderImports;
this.formatJavadoc = formatJavadoc;
this.cleanup = cleanup;
this.maxLineLength = maxLineLength;
}

/** Creates a step which formats everything - code, import order, and unused imports. */
Expand Down Expand Up @@ -93,31 +107,54 @@ public static FormatterStep create(String groupArtifact, String version, String

/** Creates a step which formats everything - groupArtifact, code, import order, and unused imports - and optionally reflows long strings. */
public static FormatterStep create(String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports, boolean formatJavadoc) {
return create(groupArtifact, version, style, provisioner, reflowLongStrings, reorderImports, formatJavadoc, DEFAULT_CLEANUP);
return create(groupArtifact, version, style, provisioner, reflowLongStrings, reorderImports, formatJavadoc, DEFAULT_CLEANUP, DEFAULT_MAX_LINE_LENGTH);
}

/**
* Creates a step which formats everything - groupArtifact, code, import order, and unused imports - and optionally reflows long strings.
* A positive {@code maxLineLength} overrides the column limit prescribed by the style, and requires google-java-format {@code 2.0.0} or later.
*/
public static FormatterStep create(String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports, boolean formatJavadoc, int maxLineLength) {
return create(groupArtifact, version, style, provisioner, reflowLongStrings, reorderImports, formatJavadoc, DEFAULT_CLEANUP, maxLineLength);
}

/**
* Creates a step which formats everything - groupArtifact, code, import order, and unused imports - and optionally reflows long strings.
* If {@code cleanup} is false, google-java-format 2.0.0 and later skip their non-whitespace cleanups (older versions don't have any).
*/
public static FormatterStep create(String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports, boolean formatJavadoc, boolean cleanup) {
return createInternally(NAME, groupArtifact, version, style, provisioner, reflowLongStrings, reorderImports, formatJavadoc, cleanup, false);
return create(groupArtifact, version, style, provisioner, reflowLongStrings, reorderImports, formatJavadoc, cleanup, DEFAULT_MAX_LINE_LENGTH);
}

/**
* Creates a step which formats everything - groupArtifact, code, import order, and unused imports - and optionally reflows long strings.
* A positive {@code maxLineLength} overrides the column limit prescribed by the style, and requires google-java-format {@code 2.0.0} or later.
* If {@code cleanup} is false, google-java-format 2.0.0 and later skip their non-whitespace cleanups (older versions don't have any).
*/
public static FormatterStep create(String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports, boolean formatJavadoc, boolean cleanup, int maxLineLength) {
return createInternally(NAME, groupArtifact, version, style, provisioner, reflowLongStrings, reorderImports, formatJavadoc, cleanup, maxLineLength, false);
}

static FormatterStep createRemoveUnusedImportsOnly(Provisioner provisioner) {
return createInternally(RemoveUnusedImportsStep.NAME, MAVEN_COORDINATE, defaultVersion(), defaultStyle(), provisioner, defaultReflowLongStrings(), defaultReorderImports(), defaultFormatJavadoc(), defaultCleanup(), true);
return createInternally(RemoveUnusedImportsStep.NAME, MAVEN_COORDINATE, defaultVersion(), defaultStyle(), provisioner, defaultReflowLongStrings(), defaultReorderImports(), defaultFormatJavadoc(), defaultCleanup(), DEFAULT_MAX_LINE_LENGTH, true);
}

private static FormatterStep createInternally(String name, String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports, boolean formatJavadoc, boolean cleanup, boolean removeImports) {
private static FormatterStep createInternally(String name, String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports, boolean formatJavadoc, boolean cleanup, int maxLineLength, boolean removeImports) {
Objects.requireNonNull(groupArtifact, "groupArtifact");
if (groupArtifact.chars().filter(ch -> ch == ':').count() != 1) {
throw new IllegalArgumentException("groupArtifact must be in the form 'groupId:artifactId'");
}
Objects.requireNonNull(version, "version");
Objects.requireNonNull(style, "style");
Objects.requireNonNull(provisioner, "provisioner");
if (maxLineLength < 0) {
throw new IllegalArgumentException("maxLineLength must be positive, but was: " + maxLineLength);
}
if (maxLineLength > 0 && compareVersions(version, MIN_VERSION_FOR_MAX_LINE_LENGTH) < 0) {
throw new IllegalArgumentException("maxLineLength requires google-java-format " + MIN_VERSION_FOR_MAX_LINE_LENGTH + " or later (you are using " + version + ")");
}

GoogleJavaFormatStep step = new GoogleJavaFormatStep(JarState.promise(() -> JarState.from(groupArtifact + ":" + version, provisioner)), version, style, reflowLongStrings, reorderImports, formatJavadoc, cleanup);
GoogleJavaFormatStep step = new GoogleJavaFormatStep(JarState.promise(() -> JarState.from(groupArtifact + ":" + version, provisioner)), version, style, reflowLongStrings, reorderImports, formatJavadoc, cleanup, maxLineLength);
if (removeImports) {
return FormatterStep.create(name,
step,
Expand Down Expand Up @@ -166,8 +203,36 @@ public static boolean defaultCleanup() {
return DEFAULT_CLEANUP;
}

public static int defaultMaxLineLength() {
return DEFAULT_MAX_LINE_LENGTH;
}

/** Compares dotted numeric versions, e.g. {@code compareVersions("1.36.1", "1.37.0") < 0}. */
private static int compareVersions(String left, String right) {
int[] lhs = parseVersion(left);
int[] rhs = parseVersion(right);
int numberOfElements = Math.max(lhs.length, rhs.length);
lhs = Arrays.copyOf(lhs, numberOfElements);
rhs = Arrays.copyOf(rhs, numberOfElements);
for (int i = 0; i < numberOfElements; i++) {
if (lhs[i] != rhs[i]) {
return lhs[i] > rhs[i] ? 1 : -1;
}
}
return 0;
}

private static int[] parseVersion(String version) {
try {
String withoutSuffix = version.endsWith("-SNAPSHOT") ? version.substring(0, version.length() - "-SNAPSHOT".length()) : version;
return Arrays.stream(withoutSuffix.split("\\.")).mapToInt(Integer::parseInt).toArray();
} catch (Exception e) {
throw new IllegalArgumentException("Not a semantic version: " + version, e);
}
}

private State equalityState() {
return new State(version, style, jarState.get(), reflowLongStrings, reorderImports, formatJavadoc, cleanup);
return new State(version, style, jarState.get(), reflowLongStrings, reorderImports, formatJavadoc, cleanup, maxLineLength);
}

private static final class State implements Serializable {
Expand All @@ -181,14 +246,16 @@ private static final class State implements Serializable {
private final boolean reorderImports;
private final boolean formatJavadoc;
private final boolean cleanup;
private final int maxLineLength;

State(String version,
String style,
JarState jarState,
boolean reflowLongStrings,
boolean reorderImports,
boolean formatJavadoc,
boolean cleanup) {
boolean cleanup,
int maxLineLength) {
JVM_SUPPORT.assertFormatterSupported(version);
ModuleHelper.doOpenInternalPackagesIfRequired();
this.jarState = jarState;
Expand All @@ -198,13 +265,14 @@ private static final class State implements Serializable {
this.reorderImports = reorderImports;
this.formatJavadoc = formatJavadoc;
this.cleanup = cleanup;
this.maxLineLength = maxLineLength;
}

FormatterFunc createFormat() throws Exception {
final ClassLoader classLoader = jarState.getClassLoader();
Class<?> formatterFunc = classLoader.loadClass("com.diffplug.spotless.glue.java.GoogleJavaFormatFormatterFunc");
Constructor<?> constructor = formatterFunc.getConstructor(String.class, String.class, boolean.class, boolean.class, boolean.class, boolean.class);
FormatterFunc googleJavaFormatFormatterFunc = (FormatterFunc) constructor.newInstance(version, style, reflowLongStrings, reorderImports, formatJavadoc, cleanup);
Constructor<?> constructor = formatterFunc.getConstructor(String.class, String.class, boolean.class, boolean.class, boolean.class, boolean.class, int.class);
FormatterFunc googleJavaFormatFormatterFunc = (FormatterFunc) constructor.newInstance(version, style, reflowLongStrings, reorderImports, formatJavadoc, cleanup, maxLineLength);

return JVM_SUPPORT.suggestLaterVersionOnError(version, googleJavaFormatFormatterFunc);
}
Expand Down
2 changes: 2 additions & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
## [Unreleased]
### Added
- Add `cleanup(false)` to `googleJavaFormat()` to disable google-java-format's non-whitespace cleanups. ([#3157](https://github.com/diffplug/spotless/pull/3157))
- Add `maxLineLength(int)` to `googleJavaFormat()`. ([#3151](https://github.com/diffplug/spotless/issues/3151))
Overrides the 100-column limit prescribed by the GOOGLE and AOSP styles; requires `google-java-format` `2.0.0` or later.
### Changes
- Bump default `google-java-format` version `1.36.1` -> `2.0.0`. ([#3157](https://github.com/diffplug/spotless/pull/3157))
This version applies some small non-whitespace cleanups by default (removing redundant semicolons, empty annotation parentheses, and parentheses around single lambda parameters), which can be disabled with `cleanup(false)`.
Expand Down
3 changes: 2 additions & 1 deletion plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -380,9 +380,10 @@ spotless {
googleJavaFormat()
// optional: you can specify a specific version (>= 1.8) and/or switch to AOSP style
// and/or reflow long strings
// and/or override the 100-column limit (requires google-java-format 2.0.0+)
// and/or disable non-whitespace cleanups (google-java-format >= 2.0.0)
// and/or use custom group artifact (you probably don't need this)
googleJavaFormat('1.8').aosp().reflowLongStrings().formatJavadoc(false).reorderImports(false).cleanup(false).groupArtifact('com.google.googlejavaformat:google-java-format')
googleJavaFormat('2.0.0').aosp().reflowLongStrings().maxLineLength(120).formatJavadoc(false).reorderImports(false).cleanup(false).groupArtifact('com.google.googlejavaformat:google-java-format')
```

### palantir-java-format
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ public class GoogleJavaFormatConfig {
boolean reorderImports;
boolean formatJavadoc = true;
boolean cleanup = true;
int maxLineLength = GoogleJavaFormatStep.defaultMaxLineLength();

GoogleJavaFormatConfig(String version) {
this.version = Objects.requireNonNull(version);
Expand Down Expand Up @@ -269,6 +270,16 @@ public GoogleJavaFormatConfig cleanup(boolean cleanup) {
return this;
}

/**
* Overrides the column limit prescribed by the style (100 for both GOOGLE and AOSP).
* Requires google-java-format 2.0.0 or later.
*/
public GoogleJavaFormatConfig maxLineLength(int maxLineLength) {
this.maxLineLength = maxLineLength;
replaceStep(createStep());
return this;
}

private FormatterStep createStep() {
return GoogleJavaFormatStep.create(
groupArtifact,
Expand All @@ -278,7 +289,8 @@ private FormatterStep createStep() {
reflowLongStrings,
reorderImports,
formatJavadoc,
cleanup);
cleanup,
maxLineLength);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package com.diffplug.gradle.spotless;

import static org.junit.jupiter.api.Assumptions.assumeTrue;

import java.io.IOException;

import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -101,4 +103,28 @@ void integrationWithSkipJavadocFormatting() throws IOException {
"googleJavaFormat()");
checkRunsThenUpToDate();
}

@Test
void integrationWithMaxLineLength() throws IOException {
assumeTrue(Jvm.version() >= 21, "google-java-format 2.0.0 requires JVM 21+");

setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"",
"spotless {",
" java {",
" target file('test.java')",
" googleJavaFormat('2.0.0').maxLineLength(240)",
" }",
"}");

setFile("test.java").toResource("java/googlejavaformat/JavaCodeUnformatted.test");
gradleRunner().withArguments("spotlessApply").build();
assertFile("test.java").sameAsResource("java/googlejavaformat/JavaCodeFormattedMaxLineLength240.test");

checkRunsThenUpToDate();
}
}
2 changes: 2 additions & 0 deletions plugin-maven/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
## [Unreleased]
### Added
- Add `<cleanup>false</cleanup>` to `<googleJavaFormat>` to disable google-java-format's non-whitespace cleanups. ([#3157](https://github.com/diffplug/spotless/pull/3157))
- Add a `<maxLineLength>` parameter to `<googleJavaFormat>`. ([#3151](https://github.com/diffplug/spotless/issues/3151))
Overrides the 100-column limit prescribed by the GOOGLE and AOSP styles; requires `google-java-format` `2.0.0` or later.
### Changes
- Bump default `google-java-format` version `1.36.1` -> `2.0.0`. ([#3157](https://github.com/diffplug/spotless/pull/3157))
This version applies some small non-whitespace cleanups by default (removing redundant semicolons, empty annotation parentheses, and parentheses around single lambda parameters), which can be disabled with `<cleanup>false</cleanup>`.
Expand Down
Loading