From eb0df83276d5a1fc044a95c8e4e1be5062ec14a3 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 4 Dec 2023 14:21:32 -0800 Subject: [PATCH 01/23] StepHarness (and WithFile) now have a `supportsRoundTrip` method so we can start testing round trip serialization on steps. --- .../spotless/SerializableEqualityTester.java | 4 +- .../com/diffplug/spotless/StepHarness.java | 18 ++---- .../diffplug/spotless/StepHarnessBase.java | 55 +++++++++++++++++++ .../spotless/StepHarnessWithFile.java | 16 ++---- 4 files changed, 67 insertions(+), 26 deletions(-) create mode 100644 testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java diff --git a/testlib/src/main/java/com/diffplug/spotless/SerializableEqualityTester.java b/testlib/src/main/java/com/diffplug/spotless/SerializableEqualityTester.java index 096edf62e2..635f11f305 100644 --- a/testlib/src/main/java/com/diffplug/spotless/SerializableEqualityTester.java +++ b/testlib/src/main/java/com/diffplug/spotless/SerializableEqualityTester.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,7 +71,7 @@ public void areDifferentThan() { } @SuppressWarnings("unchecked") - private static T reserialize(T input) { + static T reserialize(T input) { byte[] asBytes = LazyForwardingEquality.toBytes(input); ByteArrayInputStream byteInput = new ByteArrayInputStream(asBytes); try (ObjectInputStream objectInput = new ObjectInputStream(byteInput)) { diff --git a/testlib/src/main/java/com/diffplug/spotless/StepHarness.java b/testlib/src/main/java/com/diffplug/spotless/StepHarness.java index c611cc738a..79c759770f 100644 --- a/testlib/src/main/java/com/diffplug/spotless/StepHarness.java +++ b/testlib/src/main/java/com/diffplug/spotless/StepHarness.java @@ -21,17 +21,14 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.Arrays; -import java.util.Objects; import org.assertj.core.api.AbstractStringAssert; import org.assertj.core.api.Assertions; /** An api for testing a {@code FormatterStep} that doesn't depend on the File path. DO NOT ADD FILE SUPPORT TO THIS, use {@link StepHarnessWithFile} if you need that. */ -public class StepHarness implements AutoCloseable { - private final Formatter formatter; - +public class StepHarness extends StepHarnessBase { private StepHarness(Formatter formatter) { - this.formatter = Objects.requireNonNull(formatter); + super(formatter); } /** Creates a harness for testing steps which don't depend on the file. */ @@ -57,14 +54,14 @@ public static StepHarness forFormatter(Formatter formatter) { /** Asserts that the given element is transformed as expected, and that the result is idempotent. */ public StepHarness test(String before, String after) { - String actual = formatter.compute(LineEnding.toUnix(before), new File("")); + String actual = formatter().compute(LineEnding.toUnix(before), new File("")); assertEquals(after, actual, "Step application failed"); return testUnaffected(after); } /** Asserts that the given element is idempotent w.r.t the step under test. */ public StepHarness testUnaffected(String idempotentElement) { - String actual = formatter.compute(LineEnding.toUnix(idempotentElement), new File("")); + String actual = formatter().compute(LineEnding.toUnix(idempotentElement), new File("")); assertEquals(idempotentElement, actual, "Step is not idempotent"); return this; } @@ -88,7 +85,7 @@ public AbstractStringAssert testResourceExceptionMsg(String resourceBefore) { public AbstractStringAssert testExceptionMsg(String before) { try { - formatter.compute(LineEnding.toUnix(before), Formatter.NO_FILE_SENTINEL); + formatter().compute(LineEnding.toUnix(before), Formatter.NO_FILE_SENTINEL); throw new SecurityException("Expected exception"); } catch (Throwable e) { if (e instanceof SecurityException) { @@ -105,9 +102,4 @@ public AbstractStringAssert testExceptionMsg(String before) { } } } - - @Override - public void close() { - formatter.close(); - } } diff --git a/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java b/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java new file mode 100644 index 0000000000..95b54b7fbb --- /dev/null +++ b/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless; + +import java.util.Objects; + +import org.assertj.core.api.Assertions; + +class StepHarnessBase> implements AutoCloseable { + private final Formatter formatter; + private Formatter roundTripped; + private boolean supportsRoundTrip = false; + + protected StepHarnessBase(Formatter formatter) { + this.formatter = Objects.requireNonNull(formatter); + } + + public T supportsRoundTrip(boolean supportsRoundTrip) { + this.supportsRoundTrip = supportsRoundTrip; + return (T) this; + } + + protected Formatter formatter() { + if (!supportsRoundTrip) { + return formatter; + } else { + if (roundTripped == null) { + roundTripped = SerializableEqualityTester.reserialize(formatter); + Assertions.assertThat(roundTripped).isEqualTo(formatter); + } + return roundTripped; + } + } + + @Override + public void close() { + formatter.close(); + if (roundTripped != null) { + roundTripped.close(); + } + } +} diff --git a/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java b/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java index 9a9eb4042b..83bbbe09a1 100644 --- a/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java +++ b/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java @@ -26,13 +26,12 @@ import org.assertj.core.api.Assertions; /** An api for testing a {@code FormatterStep} that depends on the File path. */ -public class StepHarnessWithFile implements AutoCloseable { - private final Formatter formatter; +public class StepHarnessWithFile extends StepHarnessBase { private final ResourceHarness harness; private StepHarnessWithFile(ResourceHarness harness, Formatter formatter) { + super(formatter); this.harness = Objects.requireNonNull(harness); - this.formatter = Objects.requireNonNull(formatter); } /** Creates a harness for testing steps which do depend on the file. */ @@ -54,14 +53,14 @@ public static StepHarnessWithFile forFormatter(ResourceHarness harness, Formatte /** Asserts that the given element is transformed as expected, and that the result is idempotent. */ public StepHarnessWithFile test(File file, String before, String after) { - String actual = formatter.compute(LineEnding.toUnix(before), file); + String actual = formatter().compute(LineEnding.toUnix(before), file); assertEquals(after, actual, "Step application failed"); return testUnaffected(file, after); } /** Asserts that the given element is idempotent w.r.t the step under test. */ public StepHarnessWithFile testUnaffected(File file, String idempotentElement) { - String actual = formatter.compute(LineEnding.toUnix(idempotentElement), file); + String actual = formatter().compute(LineEnding.toUnix(idempotentElement), file); assertEquals(idempotentElement, actual, "Step is not idempotent"); return this; } @@ -96,7 +95,7 @@ public AbstractStringAssert testResourceExceptionMsg(String filename, String public AbstractStringAssert testExceptionMsg(File file, String before) { try { - formatter.compute(LineEnding.toUnix(before), file); + formatter().compute(LineEnding.toUnix(before), file); throw new SecurityException("Expected exception"); } catch (Throwable e) { if (e instanceof SecurityException) { @@ -110,9 +109,4 @@ public AbstractStringAssert testExceptionMsg(File file, String before) { } } } - - @Override - public void close() { - formatter.close(); - } } From e3843cd1865aecb2ed1936f3df2786aac1b0ec6b Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 4 Dec 2023 14:58:07 -0800 Subject: [PATCH 02/23] `StepHarnessBase` is now embarked on a methodical journey to make every step support roundtrip serialization. --- .../diffplug/spotless/StepHarnessBase.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java b/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java index 95b54b7fbb..f370b5d716 100644 --- a/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java +++ b/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java @@ -26,11 +26,23 @@ class StepHarnessBase> implements AutoCloseable { protected StepHarnessBase(Formatter formatter) { this.formatter = Objects.requireNonNull(formatter); - } - - public T supportsRoundTrip(boolean supportsRoundTrip) { - this.supportsRoundTrip = supportsRoundTrip; - return (T) this; + if (formatter.getSteps().size() == 1) { + // our goal is for everything to be roundtrip serializable + // the steps to get there are + // - make every individual step round-trippable + // - make the other machinery (Formatter, LineEnding, etc) round-trippable + // - done! + // + // Right now, we're still trying to get each and every single step to be round-trippable. + // You can help by add a test below, make sure that the test for that step fails, and then + // make the test pass. `FormatterStepEqualityOnStateSerialization` is a good base class for + // easily converting a step to round-trip serialization while maintaining easy and concise + // equality code. + String onlyStepName = formatter.getSteps().get(0).getName(); + if (onlyStepName.startsWith("indentWith")) { + supportsRoundTrip = true; + } + } } protected Formatter formatter() { From cb346a48f2712f80819c717a32c62f18c3c58dca Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 4 Dec 2023 15:19:14 -0800 Subject: [PATCH 03/23] Introduce a new `FormatterStep` base class for the serialized roundtrip world. --- .../java/com/diffplug/spotless/Formatter.java | 2 + ...atterStepEqualityOnStateSerialization.java | 91 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 lib/src/main/java/com/diffplug/spotless/FormatterStepEqualityOnStateSerialization.java diff --git a/lib/src/main/java/com/diffplug/spotless/Formatter.java b/lib/src/main/java/com/diffplug/spotless/Formatter.java index 68d9c443bf..5d2ca8369e 100644 --- a/lib/src/main/java/com/diffplug/spotless/Formatter.java +++ b/lib/src/main/java/com/diffplug/spotless/Formatter.java @@ -304,6 +304,8 @@ public void close() { for (FormatterStep step : steps) { if (step instanceof FormatterStepImpl.Standard) { ((FormatterStepImpl.Standard) step).cleanupFormatterFunc(); + } else if (step instanceof FormatterStepEqualityOnStateSerialization) { + ((FormatterStepEqualityOnStateSerialization) step).cleanupFormatterFunc(); } } } diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStepEqualityOnStateSerialization.java b/lib/src/main/java/com/diffplug/spotless/FormatterStepEqualityOnStateSerialization.java new file mode 100644 index 0000000000..6172d252f9 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStepEqualityOnStateSerialization.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless; + +import java.io.File; +import java.io.Serializable; +import java.util.Arrays; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Standard implementation of FormatterStep which cleanly enforces + * separation of a lazily computed "state" object whose serialized form + * is used as the basis for equality and hashCode, which is separate + * from the serialized form of the step itself, which can include absolute paths + * and such without interfering with buildcache keys. + */ +@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") +public abstract class FormatterStepEqualityOnStateSerialization implements FormatterStep, Serializable { + private static final long serialVersionUID = 1L; + + protected abstract State stateSupplier() throws Exception; + + protected abstract FormatterFunc stateToFormatter(State state) throws Exception; + + private transient FormatterFunc formatter; + private transient State stateInternal; + private transient byte[] serializedStateInternal; + + @Override + public String format(String rawUnix, File file) throws Exception { + if (formatter == null) { + formatter = stateToFormatter(state()); + } + return formatter.apply(rawUnix, file); + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } else if (getClass() != o.getClass()) { + return false; + } else { + return Arrays.equals(serializedState(), ((FormatterStepEqualityOnStateSerialization) o).serializedState()); + } + } + + @Override + public int hashCode() { + return Arrays.hashCode(serializedState()); + } + + void cleanupFormatterFunc() { + if (formatter instanceof FormatterFunc.Closeable) { + ((FormatterFunc.Closeable) formatter).close(); + formatter = null; + } + } + + private State state() throws Exception { + if (stateInternal == null) { + stateInternal = stateSupplier(); + } + return stateInternal; + } + + private byte[] serializedState() { + if (serializedStateInternal == null) { + try { + serializedStateInternal = LazyForwardingEquality.toBytes(state()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return serializedStateInternal; + } +} From decbd1bae4147405eeccdf9aab4abe29c8f99634 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 4 Dec 2023 15:29:24 -0800 Subject: [PATCH 04/23] Convert `IndentStep` to be roundtrip serializable. --- .../diffplug/spotless/generic/IndentStep.java | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java b/lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java index 4281df736c..6f092d8746 100644 --- a/lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java +++ b/lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,19 +15,38 @@ */ package com.diffplug.spotless.generic; -import java.io.Serializable; -import java.util.Objects; - import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.FormatterStepEqualityOnStateSerialization; /** Simple step which checks for consistent indentation characters. */ -public final class IndentStep { +public final class IndentStep extends FormatterStepEqualityOnStateSerialization { + private static final long serialVersionUID = 1L; - private static final int DEFAULT_NUM_SPACES_PER_TAB = 4; + final Type type; + final int numSpacesPerTab; + + private IndentStep(Type type, int numSpacesPerTab) { + this.type = type; + this.numSpacesPerTab = numSpacesPerTab; + } + + @Override + public String getName() { + return "indentWith" + type.tabSpace("Tabs", "Spaces"); + } + + @Override + protected IndentStep stateSupplier() { + return this; + } + + @Override + protected FormatterFunc stateToFormatter(IndentStep state) { + return new Runtime(this)::format; + } - // prevent direct instantiation - private IndentStep() {} + private static final int DEFAULT_NUM_SPACES_PER_TAB = 4; public enum Type { TAB, SPACE; @@ -49,32 +68,14 @@ public FormatterStep create(int numSpacesPerTab) { /** Creates a step which will indent with the given type of whitespace, converting between tabs and spaces at the given ratio. */ public static FormatterStep create(Type type, int numSpacesPerTab) { - Objects.requireNonNull(type, "type"); - return FormatterStep.create("indentWith" + type.tabSpace("Tabs", "Spaces"), - new State(type, numSpacesPerTab), State::toFormatter); - } - - private static class State implements Serializable { - private static final long serialVersionUID = 1L; - - final Type type; - final int numSpacesPerTab; - - State(Type type, int numSpacesPerTab) { - this.type = type; - this.numSpacesPerTab = numSpacesPerTab; - } - - FormatterFunc toFormatter() { - return new Runtime(this)::format; - } + return new IndentStep(type, numSpacesPerTab); } static class Runtime { - final State state; + final IndentStep state; final StringBuilder builder = new StringBuilder(); - Runtime(State state) { + Runtime(IndentStep state) { this.state = state; } From 317b2ac571e1956bc06013a6a15473366b665bab Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 4 Dec 2023 15:58:25 -0800 Subject: [PATCH 05/23] Remove the long-deprecated and completely unused `Set mavenCoordinates` field from `JarState`. --- .../spotless/extra/EclipseBasedStepBuilder.java | 9 +-------- .../java/com/diffplug/spotless/JarState.java | 16 +++------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/EclipseBasedStepBuilder.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/EclipseBasedStepBuilder.java index 4f48a5ed74..0cf14eb32c 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/EclipseBasedStepBuilder.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/EclipseBasedStepBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.Properties; import com.diffplug.common.base.Errors; @@ -187,12 +186,6 @@ public Properties getPreferences() { return preferences.getProperties(); } - /** Returns first coordinate from sorted set that starts with a given prefix.*/ - public Optional getMavenCoordinate(String prefix) { - return jarState.getMavenCoordinates().stream() - .filter(coordinate -> coordinate.startsWith(prefix)).findFirst(); - } - /** * Load class based on the given configuration of JAR provider and Maven coordinates. * Different class loader instances are provided in the following scenarios: diff --git a/lib/src/main/java/com/diffplug/spotless/JarState.java b/lib/src/main/java/com/diffplug/spotless/JarState.java index 48686c49f8..3dd46a9f3e 100644 --- a/lib/src/main/java/com/diffplug/spotless/JarState.java +++ b/lib/src/main/java/com/diffplug/spotless/JarState.java @@ -25,7 +25,6 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; -import java.util.TreeSet; import java.util.stream.Collectors; /** @@ -39,12 +38,9 @@ public final class JarState implements Serializable { private static final long serialVersionUID = 1L; - @Deprecated - private final Set mavenCoordinates; private final FileSignature fileSignature; - private JarState(Collection mavenCoordinates, FileSignature fileSignature) { - this.mavenCoordinates = new TreeSet<>(mavenCoordinates); + private JarState(FileSignature fileSignature) { this.fileSignature = fileSignature; } @@ -71,13 +67,13 @@ private static JarState provisionWithTransitives(boolean withTransitives, Collec throw new NoSuchElementException("Resolved to an empty result: " + mavenCoordinates.stream().collect(Collectors.joining(", "))); } FileSignature fileSignature = FileSignature.signAsSet(jars); - return new JarState(mavenCoordinates, fileSignature); + return new JarState(fileSignature); } /** Wraps the given collection of a files as a JarState, maintaining the order in the Collection. */ public static JarState preserveOrder(Collection jars) throws IOException { FileSignature fileSignature = FileSignature.signAsList(jars); - return new JarState(Collections.emptySet(), fileSignature); + return new JarState(fileSignature); } URL[] jarUrls() { @@ -107,10 +103,4 @@ public ClassLoader getClassLoader() { public ClassLoader getClassLoader(Serializable key) { return SpotlessCache.instance().classloader(key, this); } - - /** Returns unmodifiable view on sorted Maven coordinates */ - @Deprecated - public Set getMavenCoordinates() { - return Collections.unmodifiableSet(mavenCoordinates); - } } From e4d6c494443ace8a86ec3aabeeee9bf47f2806cb Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 4 Dec 2023 16:21:34 -0800 Subject: [PATCH 06/23] Introduce `FileSignature.RoundTrippable` and `JarState.Promised` so that maven-based formatters can be safely roundtripped. --- .../com/diffplug/spotless/FileSignature.java | 44 ++++++++++++++++++- .../java/com/diffplug/spotless/JarState.java | 43 ++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/com/diffplug/spotless/FileSignature.java b/lib/src/main/java/com/diffplug/spotless/FileSignature.java index 59893f26e6..e31337e39c 100644 --- a/lib/src/main/java/com/diffplug/spotless/FileSignature.java +++ b/lib/src/main/java/com/diffplug/spotless/FileSignature.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import java.util.Locale; import java.util.Map; +import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** Computes a signature for any needed files. */ @@ -43,6 +44,8 @@ public final class FileSignature implements Serializable { * Transient because not needed to uniquely identify a FileSignature instance, and also because * Gradle only needs this class to be Serializable so it can compare FileSignature instances for * incremental builds. + * + * We don't want these absolute paths to screw up buildcache keys. */ @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") private final transient List files; @@ -93,6 +96,45 @@ private FileSignature(final List files) throws IOException { } } + /** A view of `FileSignature` which can be safely roundtripped. */ + public static class RoundTrippable implements Serializable { + private final List files; + private transient @Nullable FileSignature cached; + + private RoundTrippable(List files, FileSignature cached) { + this.files = files; + this.cached = cached; + } + + public FileSignature stripAbsolutePaths() throws IOException { + if (cached == null) { + // null when restored via serialization + cached = new FileSignature(files); + } + return cached; + } + } + + public RoundTrippable roundTrippable() { + return new RoundTrippable(files, this); + } + + public static @Nullable RoundTrippable roundTrippableNullable(@Nullable FileSignature signature) { + if (signature != null) { + return signature.roundTrippable(); + } else { + return null; + } + } + + public static @Nullable FileSignature stripAbsolutePathsNullable(@Nullable RoundTrippable roundTrippable) throws IOException { + if (roundTrippable != null) { + return roundTrippable.stripAbsolutePaths(); + } else { + return null; + } + } + /** Returns all of the files in this signature, throwing an exception if there are more or less than 1 file. */ public Collection files() { return Collections.unmodifiableList(files); diff --git a/lib/src/main/java/com/diffplug/spotless/JarState.java b/lib/src/main/java/com/diffplug/spotless/JarState.java index 3dd46a9f3e..5a52b4af6d 100644 --- a/lib/src/main/java/com/diffplug/spotless/JarState.java +++ b/lib/src/main/java/com/diffplug/spotless/JarState.java @@ -17,6 +17,7 @@ import java.io.File; import java.io.IOException; +import java.io.ObjectStreamException; import java.io.Serializable; import java.net.URI; import java.net.URL; @@ -36,6 +37,48 @@ * catch changes in a SNAPSHOT version. */ public final class JarState implements Serializable { + /** A lazily evaluated JarState, which becomes a set of files when serialized. */ + public static class Promised implements Serializable { + private final transient ThrowingEx.Supplier supplier; + private FileSignature.RoundTrippable cached; + + public Promised(ThrowingEx.Supplier supplier) { + this.supplier = supplier; + } + + public JarState get() { + try { + if (cached == null) { + JarState result = supplier.get(); + cached = result.fileSignature.roundTrippable(); + return result; + } + return new JarState(cached.stripAbsolutePaths()); + } catch (Exception e) { + throw ThrowingEx.asRuntime(e); + } + } + + // override serialize output + private void writeObject(java.io.ObjectOutputStream out) + throws IOException { + get(); + out.defaultWriteObject(); + } + + private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + } + + private void readObjectNoData() throws ObjectStreamException { + throw new UnsupportedOperationException(); + } + } + + public static Promised promise(ThrowingEx.Supplier supplier) { + return new Promised(supplier); + } + private static final long serialVersionUID = 1L; private final FileSignature fileSignature; From 6f7edd33ec2e7f7ccb273fe36761eb4923547168 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 4 Dec 2023 16:21:52 -0800 Subject: [PATCH 07/23] Make `DiktatStep` round-trippable. --- .../diffplug/spotless/kotlin/DiktatStep.java | 43 +++++++++++++------ .../diffplug/spotless/StepHarnessBase.java | 2 + 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java index a2650e31fc..1e4b6ef7d8 100644 --- a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java +++ b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java @@ -25,10 +25,16 @@ import com.diffplug.spotless.*; /** Wraps up diktat as a FormatterStep. */ -public class DiktatStep { - - // prevent direct instantiation - private DiktatStep() {} +public class DiktatStep extends FormatterStepEqualityOnStateSerialization { + private final JarState.Promised jarState; + private final boolean isScript; + private final @Nullable FileSignature.RoundTrippable config; + + private DiktatStep(JarState.Promised jarState, boolean isScript, @Nullable FileSignature config) { + this.jarState = jarState; + this.isScript = isScript; + this.config = FileSignature.roundTrippableNullable(config); + } private static final String MIN_SUPPORTED_VERSION = "1.2.1"; @@ -59,26 +65,35 @@ public static FormatterStep create(String versionDiktat, Provisioner provisioner } Objects.requireNonNull(versionDiktat, "versionDiktat"); Objects.requireNonNull(provisioner, "provisioner"); - return FormatterStep.createLazy(NAME, - () -> new DiktatStep.State(versionDiktat, provisioner, isScript, config), - DiktatStep.State::createFormat); + return new DiktatStep(JarState.promise(() -> JarState.from(MAVEN_COORDINATE + versionDiktat, provisioner)), isScript, config); + } + + @Override + public String getName() { + return NAME; + } + + @Override + protected State stateSupplier() throws Exception { + return new State(jarState.get(), isScript, FileSignature.stripAbsolutePathsNullable(config)); + } + + @Override + protected FormatterFunc stateToFormatter(State state) throws Exception { + return state.createFormat(); } static final class State implements Serializable { private static final long serialVersionUID = 1L; + final JarState jar; /** Are the files being linted Kotlin script files. */ private final boolean isScript; private final @Nullable FileSignature config; - final JarState jar; - - State(String versionDiktat, Provisioner provisioner, boolean isScript, @Nullable FileSignature config) throws IOException { - - HashSet pkgSet = new HashSet<>(); - pkgSet.add(MAVEN_COORDINATE + versionDiktat); - this.jar = JarState.from(pkgSet, provisioner); + State(JarState jar, boolean isScript, @Nullable FileSignature config) throws IOException { + this.jar = jar; this.isScript = isScript; this.config = config; } diff --git a/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java b/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java index f370b5d716..0a0154176b 100644 --- a/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java +++ b/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java @@ -41,6 +41,8 @@ protected StepHarnessBase(Formatter formatter) { String onlyStepName = formatter.getSteps().get(0).getName(); if (onlyStepName.startsWith("indentWith")) { supportsRoundTrip = true; + } else if (onlyStepName.equals("diktat")) { + supportsRoundTrip = true; } } } From 9fa1da3126ab55898f2d41e243aebd67d302024c Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 4 Dec 2023 16:28:54 -0800 Subject: [PATCH 08/23] Fix spotbugs warnings. --- lib/src/main/java/com/diffplug/spotless/FileSignature.java | 2 ++ lib/src/main/java/com/diffplug/spotless/JarState.java | 1 + lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java | 1 + 3 files changed, 4 insertions(+) diff --git a/lib/src/main/java/com/diffplug/spotless/FileSignature.java b/lib/src/main/java/com/diffplug/spotless/FileSignature.java index e31337e39c..05777ccdcc 100644 --- a/lib/src/main/java/com/diffplug/spotless/FileSignature.java +++ b/lib/src/main/java/com/diffplug/spotless/FileSignature.java @@ -98,7 +98,9 @@ private FileSignature(final List files) throws IOException { /** A view of `FileSignature` which can be safely roundtripped. */ public static class RoundTrippable implements Serializable { + private static final long serialVersionUID = 1L; private final List files; + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") private transient @Nullable FileSignature cached; private RoundTrippable(List files, FileSignature cached) { diff --git a/lib/src/main/java/com/diffplug/spotless/JarState.java b/lib/src/main/java/com/diffplug/spotless/JarState.java index 5a52b4af6d..70de7fb7c5 100644 --- a/lib/src/main/java/com/diffplug/spotless/JarState.java +++ b/lib/src/main/java/com/diffplug/spotless/JarState.java @@ -39,6 +39,7 @@ public final class JarState implements Serializable { /** A lazily evaluated JarState, which becomes a set of files when serialized. */ public static class Promised implements Serializable { + private static final long serialVersionUID = 1L; private final transient ThrowingEx.Supplier supplier; private FileSignature.RoundTrippable cached; diff --git a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java index 1e4b6ef7d8..9ad96797e9 100644 --- a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java +++ b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java @@ -26,6 +26,7 @@ /** Wraps up diktat as a FormatterStep. */ public class DiktatStep extends FormatterStepEqualityOnStateSerialization { + private static final long serialVersionUID = 1L; private final JarState.Promised jarState; private final boolean isScript; private final @Nullable FileSignature.RoundTrippable config; From cce20d45e78788189212d2ca89bda460927173bb Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 5 Dec 2023 11:54:35 -0800 Subject: [PATCH 09/23] Rename `FileSignature.RoundTrippable` to `FileSignature.Promised` to match `JarState`'s naming convention. --- .../com/diffplug/spotless/FileSignature.java | 16 ++++++++++------ .../java/com/diffplug/spotless/JarState.java | 2 +- .../com/diffplug/spotless/kotlin/DiktatStep.java | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/FileSignature.java b/lib/src/main/java/com/diffplug/spotless/FileSignature.java index 05777ccdcc..92518cca17 100644 --- a/lib/src/main/java/com/diffplug/spotless/FileSignature.java +++ b/lib/src/main/java/com/diffplug/spotless/FileSignature.java @@ -97,13 +97,13 @@ private FileSignature(final List files) throws IOException { } /** A view of `FileSignature` which can be safely roundtripped. */ - public static class RoundTrippable implements Serializable { + public static class Promised implements Serializable { private static final long serialVersionUID = 1L; private final List files; @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") private transient @Nullable FileSignature cached; - private RoundTrippable(List files, FileSignature cached) { + private Promised(List files, FileSignature cached) { this.files = files; this.cached = cached; } @@ -117,11 +117,15 @@ public FileSignature stripAbsolutePaths() throws IOException { } } - public RoundTrippable roundTrippable() { - return new RoundTrippable(files, this); + public static Promised promise(Iterable files) { + return new Promised(MoreIterables.toNullHostileList(files), null); } - public static @Nullable RoundTrippable roundTrippableNullable(@Nullable FileSignature signature) { + public Promised roundTrippable() { + return new Promised(files, this); + } + + public static @Nullable Promised roundTrippableNullable(@Nullable FileSignature signature) { if (signature != null) { return signature.roundTrippable(); } else { @@ -129,7 +133,7 @@ public RoundTrippable roundTrippable() { } } - public static @Nullable FileSignature stripAbsolutePathsNullable(@Nullable RoundTrippable roundTrippable) throws IOException { + public static @Nullable FileSignature stripAbsolutePathsNullable(@Nullable Promised roundTrippable) throws IOException { if (roundTrippable != null) { return roundTrippable.stripAbsolutePaths(); } else { diff --git a/lib/src/main/java/com/diffplug/spotless/JarState.java b/lib/src/main/java/com/diffplug/spotless/JarState.java index 70de7fb7c5..659894a40a 100644 --- a/lib/src/main/java/com/diffplug/spotless/JarState.java +++ b/lib/src/main/java/com/diffplug/spotless/JarState.java @@ -41,7 +41,7 @@ public final class JarState implements Serializable { public static class Promised implements Serializable { private static final long serialVersionUID = 1L; private final transient ThrowingEx.Supplier supplier; - private FileSignature.RoundTrippable cached; + private FileSignature.Promised cached; public Promised(ThrowingEx.Supplier supplier) { this.supplier = supplier; diff --git a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java index 9ad96797e9..6e01eda91e 100644 --- a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java +++ b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java @@ -29,7 +29,7 @@ public class DiktatStep extends FormatterStepEqualityOnStateSerialization Date: Tue, 5 Dec 2023 19:18:16 -0800 Subject: [PATCH 10/23] More improvements to `StepHarnessWithFile`. --- .../com/diffplug/spotless/StepHarnessWithFile.java | 11 ++++++++--- .../spotless/generic/LicenseHeaderStepTest.java | 11 ++++------- .../spotless/npm/EslintFormatterStepTest.java | 9 +++------ 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java b/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java index 83bbbe09a1..a557b76017 100644 --- a/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java +++ b/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java @@ -52,7 +52,8 @@ public static StepHarnessWithFile forFormatter(ResourceHarness harness, Formatte } /** Asserts that the given element is transformed as expected, and that the result is idempotent. */ - public StepHarnessWithFile test(File file, String before, String after) { + public StepHarnessWithFile test(String filename, String before, String after) { + File file = harness.setFile(filename).toContent(before); String actual = formatter().compute(LineEnding.toUnix(before), file); assertEquals(after, actual, "Step application failed"); return testUnaffected(file, after); @@ -65,6 +66,11 @@ public StepHarnessWithFile testUnaffected(File file, String idempotentElement) { return this; } + /** Asserts that the given element is idempotent w.r.t the step under test. */ + public StepHarnessWithFile testUnaffected(String file, String idempotentElement) { + return testUnaffected(harness.setFile(file).toContent(idempotentElement), idempotentElement); + } + /** Asserts that the given elements in the resources directory are transformed as expected. */ public StepHarnessWithFile testResource(String resourceBefore, String resourceAfter) { return testResource(resourceBefore, resourceBefore, resourceAfter); @@ -72,8 +78,7 @@ public StepHarnessWithFile testResource(String resourceBefore, String resourceAf public StepHarnessWithFile testResource(String filename, String resourceBefore, String resourceAfter) { String contentBefore = ResourceHarness.getTestResource(resourceBefore); - File file = harness.setFile(filename).toContent(contentBefore); - return test(file, contentBefore, ResourceHarness.getTestResource(resourceAfter)); + return test(filename, contentBefore, ResourceHarness.getTestResource(resourceAfter)); } /** Asserts that the given elements in the resources directory are transformed as expected. */ diff --git a/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java b/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java index 92c6794643..a9ec563e58 100644 --- a/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java @@ -268,7 +268,7 @@ void should_preserve_year_for_license_with_address() throws Throwable { void should_apply_license_containing_filename_token() throws Exception { FormatterStep step = LicenseHeaderStep.headerDelimiter(header(HEADER_WITH_$FILE), package_).build(); StepHarnessWithFile.forStep(this, step) - .test(new File("Test.java"), getTestResource(FILE_NO_LICENSE), hasHeaderFileName(HEADER_WITH_$FILE, "Test.java")) + .test("Test.java", getTestResource(FILE_NO_LICENSE), hasHeaderFileName(HEADER_WITH_$FILE, "Test.java")) .testUnaffected(new File("Test.java"), hasHeaderFileName(HEADER_WITH_$FILE, "Test.java")); } @@ -276,8 +276,7 @@ void should_apply_license_containing_filename_token() throws Exception { void should_update_license_containing_filename_token() throws Exception { FormatterStep step = LicenseHeaderStep.headerDelimiter(header(HEADER_WITH_$FILE), package_).build(); StepHarnessWithFile.forStep(this, step) - .test( - new File("After.java"), + .test("After.java", hasHeaderFileName(HEADER_WITH_$FILE, "Before.java"), hasHeaderFileName(HEADER_WITH_$FILE, "After.java")); } @@ -286,12 +285,10 @@ void should_update_license_containing_filename_token() throws Exception { void should_apply_license_containing_YEAR_filename_token() throws Exception { FormatterStep step = LicenseHeaderStep.headerDelimiter(header(HEADER_WITH_$YEAR_$FILE), package_).build(); StepHarnessWithFile.forStep(this, step) - .test( - new File("Test.java"), + .test("Test.java", getTestResource(FILE_NO_LICENSE), hasHeaderYearFileName(HEADER_WITH_$YEAR_$FILE, currentYear(), "Test.java")) - .testUnaffected( - new File("Test.java"), + .testUnaffected("Test.java", hasHeaderYearFileName(HEADER_WITH_$YEAR_$FILE, currentYear(), "Test.java")); } diff --git a/testlib/src/test/java/com/diffplug/spotless/npm/EslintFormatterStepTest.java b/testlib/src/test/java/com/diffplug/spotless/npm/EslintFormatterStepTest.java index 631a4beaa7..1b365e8d11 100644 --- a/testlib/src/test/java/com/diffplug/spotless/npm/EslintFormatterStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/npm/EslintFormatterStepTest.java @@ -56,7 +56,6 @@ void formattingUsingRulesetsFile(String ruleSetName) throws Exception { // final File eslintRc = setFile(buildDir().getPath() + "/.eslintrc.js").toResource(filedir + ".eslintrc.js"); final String dirtyFile = filedir + "javascript-es6.dirty"; - File dirtyFileFile = setFile(testDir + "test.js").toResource(dirtyFile); final String cleanFile = filedir + "javascript-es6.clean"; final FormatterStep formatterStep = EslintFormatterStep.create( @@ -69,7 +68,7 @@ void formattingUsingRulesetsFile(String ruleSetName) throws Exception { new EslintConfig(eslintRc, null)); try (StepHarnessWithFile stepHarness = StepHarnessWithFile.forStep(this, formatterStep)) { - stepHarness.test(dirtyFileFile, ResourceHarness.getTestResource(dirtyFile), ResourceHarness.getTestResource(cleanFile)); + stepHarness.test("test.js", ResourceHarness.getTestResource(dirtyFile), ResourceHarness.getTestResource(cleanFile)); } } } @@ -100,7 +99,6 @@ void formattingUsingRulesetsFile(String ruleSetName) throws Exception { tsconfigFile = setFile(testDir + "tsconfig.json").toResource(filedir + "tsconfig.json"); } final String dirtyFile = filedir + "typescript.dirty"; - File dirtyFileFile = setFile(testDir + "test.ts").toResource(dirtyFile); final String cleanFile = filedir + "typescript.clean"; final FormatterStep formatterStep = EslintFormatterStep.create( @@ -113,7 +111,7 @@ void formattingUsingRulesetsFile(String ruleSetName) throws Exception { new EslintTypescriptConfig(eslintRc, null, tsconfigFile)); try (StepHarnessWithFile stepHarness = StepHarnessWithFile.forStep(this, formatterStep)) { - stepHarness.test(dirtyFileFile, ResourceHarness.getTestResource(dirtyFile), ResourceHarness.getTestResource(cleanFile)); + stepHarness.test(testDir + "test.ts", ResourceHarness.getTestResource(dirtyFile), ResourceHarness.getTestResource(cleanFile)); } } } @@ -158,7 +156,6 @@ void formattingUsingInlineXoConfig() throws Exception { File tsconfigFile = setFile(testDir + "tsconfig.json").toResource(filedir + "tsconfig.json"); final String dirtyFile = filedir + "typescript.dirty"; - File dirtyFileFile = setFile(testDir + "test.ts").toResource(dirtyFile); final String cleanFile = filedir + "typescript.clean"; final FormatterStep formatterStep = EslintFormatterStep.create( @@ -171,7 +168,7 @@ void formattingUsingInlineXoConfig() throws Exception { new EslintTypescriptConfig(null, esLintConfig, tsconfigFile)); try (StepHarnessWithFile stepHarness = StepHarnessWithFile.forStep(this, formatterStep)) { - stepHarness.test(dirtyFileFile, ResourceHarness.getTestResource(dirtyFile), ResourceHarness.getTestResource(cleanFile)); + stepHarness.test(testDir + "test.ts", ResourceHarness.getTestResource(dirtyFile), ResourceHarness.getTestResource(cleanFile)); } } } From c56968af79218da1935fb351ed30556ba83178d2 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 5 Dec 2023 19:17:01 -0800 Subject: [PATCH 11/23] Add a roundtrip-serializable method to `FormatterStep`. --- .../com/diffplug/spotless/FormatterStep.java | 23 ++++++ .../FormatterStepSerializationRoundtrip.java | 71 +++++++++++++++++++ .../diffplug/spotless/SerializedFunction.java | 21 ++++++ 3 files changed, 115 insertions(+) create mode 100644 lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java create mode 100644 lib/src/main/java/com/diffplug/spotless/SerializedFunction.java diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java index ca37303b1f..ec3880a098 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java @@ -104,6 +104,29 @@ public final String format(String rawUnix, File file) throws Exception { } } + /** + * @param name + * The name of the formatter step. + * @param roundtripInit + * If the step has any state, this supplier will calculate it lazily. The supplier doesn't + * have to be serializable, but the result it calculates needs to be serializable. + * @param equalityFunc + * A pure serializable function (method reference recommended) which takes the result of `roundtripInit`, + * and returns a serializable object whose serialized representation will be used for `.equals` and + * `.hashCode` of the FormatterStep. + * @param formatterFunc + * A pure serializable function (method reference recommended) which takes the result of `equalityFunc`, + * and returns a `FormatterFunc` which will be used for the actual formatting. + * @return A FormatterStep which can be losslessly roundtripped through the java serialization machinery. + */ + static FormatterStep createLazy( + String name, + ThrowingEx.Supplier roundtripInit, + SerializedFunction equalityFunc, + SerializedFunction formatterFunc) { + return new FormatterStepSerializationRoundtrip(name, roundtripInit, equalityFunc, formatterFunc); + } + /** * @param name * The name of the formatter step diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java b/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java new file mode 100644 index 0000000000..5eb46b8cab --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java @@ -0,0 +1,71 @@ +/* + * Copyright 2023 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless; + +import java.io.IOException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +import edu.umd.cs.findbugs.annotations.Nullable; + +class FormatterStepSerializationRoundtrip extends FormatterStepEqualityOnStateSerialization { + private final String name; + private final transient ThrowingEx.Supplier initializer; + private @Nullable RoundtripState roundtripStateInternal; + private final SerializedFunction equalityStateExtractor; + private final SerializedFunction equalityStateToFormatter; + + public FormatterStepSerializationRoundtrip(String name, ThrowingEx.Supplier initializer, SerializedFunction equalityStateExtractor, SerializedFunction equalityStateToFormatter) { + this.name = name; + this.initializer = initializer; + this.equalityStateExtractor = equalityStateExtractor; + this.equalityStateToFormatter = equalityStateToFormatter; + } + + @Override + public String getName() { + return name; + } + + @Override + protected EqualityState stateSupplier() throws Exception { + if (roundtripStateInternal != null) { + roundtripStateInternal = initializer.get(); + } + return equalityStateExtractor.apply(roundtripStateInternal); + } + + @Override + protected FormatterFunc stateToFormatter(EqualityState equalityState) throws Exception { + return equalityStateToFormatter.apply(equalityState); + } + + // override serialize output + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + if (roundtripStateInternal == null) { + roundtripStateInternal = ThrowingEx.get(initializer::get); + } + out.defaultWriteObject(); + } + + private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + } + + private void readObjectNoData() throws ObjectStreamException { + throw new UnsupportedOperationException(); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/SerializedFunction.java b/lib/src/main/java/com/diffplug/spotless/SerializedFunction.java new file mode 100644 index 0000000000..5af924a871 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/SerializedFunction.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless; + +import java.io.Serializable; + +@FunctionalInterface +interface SerializedFunction extends Serializable, ThrowingEx.Function {} From 46635c61cb424f9909757f2197b2a4ece5fe051b Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 5 Dec 2023 23:49:23 -0800 Subject: [PATCH 12/23] Fixup the `FormatterStep` api. --- .../com/diffplug/spotless/FormatterStep.java | 22 +++++++++++++++++++ ...atterStepEqualityOnStateSerialization.java | 8 ++----- .../FormatterStepSerializationRoundtrip.java | 2 +- .../diffplug/spotless/SerializedFunction.java | 6 ++++- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java index ec3880a098..fdaa9cc068 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java @@ -127,6 +127,28 @@ static return new FormatterStepSerializationRoundtrip(name, roundtripInit, equalityFunc, formatterFunc); } + /** + * @param name + * The name of the formatter step. + * @param roundTrip + * The roundtrip serializable state of the step. + * @param equalityFunc + * A pure serializable function (method reference recommended) which takes the result of `roundTrip`, + * and returns a serializable object whose serialized representation will be used for `.equals` and + * `.hashCode` of the FormatterStep. + * @param formatterFunc + * A pure serializable function (method reference recommended) which takes the result of `equalityFunc`, + * and returns a `FormatterFunc` which will be used for the actual formatting. + * @return A FormatterStep which can be losslessly roundtripped through the java serialization machinery. + */ + static FormatterStep create( + String name, + RoundtripState roundTrip, + SerializedFunction equalityFunc, + SerializedFunction formatterFunc) { + return createLazy(name, () -> roundTrip, equalityFunc, formatterFunc); + } + /** * @param name * The name of the formatter step diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStepEqualityOnStateSerialization.java b/lib/src/main/java/com/diffplug/spotless/FormatterStepEqualityOnStateSerialization.java index 6172d252f9..ca7fa7cfc0 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStepEqualityOnStateSerialization.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStepEqualityOnStateSerialization.java @@ -29,7 +29,7 @@ * and such without interfering with buildcache keys. */ @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") -public abstract class FormatterStepEqualityOnStateSerialization implements FormatterStep, Serializable { +abstract class FormatterStepEqualityOnStateSerialization implements FormatterStep, Serializable { private static final long serialVersionUID = 1L; protected abstract State stateSupplier() throws Exception; @@ -80,11 +80,7 @@ private State state() throws Exception { private byte[] serializedState() { if (serializedStateInternal == null) { - try { - serializedStateInternal = LazyForwardingEquality.toBytes(state()); - } catch (Exception e) { - throw new RuntimeException(e); - } + serializedStateInternal = ThrowingEx.get(() -> LazyForwardingEquality.toBytes(state())); } return serializedStateInternal; } diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java b/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java index 5eb46b8cab..9152fcdeac 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java @@ -42,7 +42,7 @@ public String getName() { @Override protected EqualityState stateSupplier() throws Exception { - if (roundtripStateInternal != null) { + if (roundtripStateInternal == null) { roundtripStateInternal = initializer.get(); } return equalityStateExtractor.apply(roundtripStateInternal); diff --git a/lib/src/main/java/com/diffplug/spotless/SerializedFunction.java b/lib/src/main/java/com/diffplug/spotless/SerializedFunction.java index 5af924a871..56f5974870 100644 --- a/lib/src/main/java/com/diffplug/spotless/SerializedFunction.java +++ b/lib/src/main/java/com/diffplug/spotless/SerializedFunction.java @@ -18,4 +18,8 @@ import java.io.Serializable; @FunctionalInterface -interface SerializedFunction extends Serializable, ThrowingEx.Function {} +public interface SerializedFunction extends Serializable, ThrowingEx.Function { + static SerializedFunction identity() { + return t -> t; + } +} From f5f683678001b5ad8f54fe04c0a53d57cabee694 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 5 Dec 2023 23:50:53 -0800 Subject: [PATCH 13/23] Use the new API. --- .../diffplug/spotless/generic/IndentStep.java | 30 ++++++++----------- .../diffplug/spotless/kotlin/DiktatStep.java | 20 ++++--------- 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java b/lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java index 6f092d8746..5eb7c0f758 100644 --- a/lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java +++ b/lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java @@ -15,12 +15,14 @@ */ package com.diffplug.spotless.generic; +import java.io.Serializable; + import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.FormatterStepEqualityOnStateSerialization; +import com.diffplug.spotless.SerializedFunction; /** Simple step which checks for consistent indentation characters. */ -public final class IndentStep extends FormatterStepEqualityOnStateSerialization { +public final class IndentStep implements Serializable { private static final long serialVersionUID = 1L; final Type type; @@ -31,21 +33,6 @@ private IndentStep(Type type, int numSpacesPerTab) { this.numSpacesPerTab = numSpacesPerTab; } - @Override - public String getName() { - return "indentWith" + type.tabSpace("Tabs", "Spaces"); - } - - @Override - protected IndentStep stateSupplier() { - return this; - } - - @Override - protected FormatterFunc stateToFormatter(IndentStep state) { - return new Runtime(this)::format; - } - private static final int DEFAULT_NUM_SPACES_PER_TAB = 4; public enum Type { @@ -68,7 +55,14 @@ public FormatterStep create(int numSpacesPerTab) { /** Creates a step which will indent with the given type of whitespace, converting between tabs and spaces at the given ratio. */ public static FormatterStep create(Type type, int numSpacesPerTab) { - return new IndentStep(type, numSpacesPerTab); + return FormatterStep.create("indentWith" + type.tabSpace("Tabs", "Spaces"), + new IndentStep(type, numSpacesPerTab), SerializedFunction.identity(), + IndentStep::startFormatting); + } + + private FormatterFunc startFormatting() { + var runtime = new Runtime(this); + return runtime::format; } static class Runtime { diff --git a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java index 6e01eda91e..9694033752 100644 --- a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java +++ b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java @@ -25,7 +25,7 @@ import com.diffplug.spotless.*; /** Wraps up diktat as a FormatterStep. */ -public class DiktatStep extends FormatterStepEqualityOnStateSerialization { +public class DiktatStep implements Serializable { private static final long serialVersionUID = 1L; private final JarState.Promised jarState; private final boolean isScript; @@ -66,26 +66,16 @@ public static FormatterStep create(String versionDiktat, Provisioner provisioner } Objects.requireNonNull(versionDiktat, "versionDiktat"); Objects.requireNonNull(provisioner, "provisioner"); - return new DiktatStep(JarState.promise(() -> JarState.from(MAVEN_COORDINATE + versionDiktat, provisioner)), isScript, config); + return FormatterStep.create(NAME, + new DiktatStep(JarState.promise(() -> JarState.from(MAVEN_COORDINATE + versionDiktat, provisioner)), isScript, config), + DiktatStep::equalityState, State::createFormat); } - @Override - public String getName() { - return NAME; - } - - @Override - protected State stateSupplier() throws Exception { + private State equalityState() throws Exception { return new State(jarState.get(), isScript, FileSignature.stripAbsolutePathsNullable(config)); } - @Override - protected FormatterFunc stateToFormatter(State state) throws Exception { - return state.createFormat(); - } - static final class State implements Serializable { - private static final long serialVersionUID = 1L; final JarState jar; From 44b3126ff4968fbe01c891ce8f69006046dd8219 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 5 Dec 2023 23:53:27 -0800 Subject: [PATCH 14/23] Fixup spotbugs warnings. --- lib/src/main/java/com/diffplug/spotless/FileSignature.java | 2 +- .../diffplug/spotless/FormatterStepSerializationRoundtrip.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/com/diffplug/spotless/FileSignature.java b/lib/src/main/java/com/diffplug/spotless/FileSignature.java index 92518cca17..9bbc62b9ec 100644 --- a/lib/src/main/java/com/diffplug/spotless/FileSignature.java +++ b/lib/src/main/java/com/diffplug/spotless/FileSignature.java @@ -103,7 +103,7 @@ public static class Promised implements Serializable { @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") private transient @Nullable FileSignature cached; - private Promised(List files, FileSignature cached) { + private Promised(List files, @Nullable FileSignature cached) { this.files = files; this.cached = cached; } diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java b/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java index 9152fcdeac..d996c0b360 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java @@ -22,6 +22,7 @@ import edu.umd.cs.findbugs.annotations.Nullable; class FormatterStepSerializationRoundtrip extends FormatterStepEqualityOnStateSerialization { + private static final long serialVersionUID = 1L; private final String name; private final transient ThrowingEx.Supplier initializer; private @Nullable RoundtripState roundtripStateInternal; From 55481fa8cc07fe4525f5f69683bfa668157a74b4 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 6 Dec 2023 00:04:53 -0800 Subject: [PATCH 15/23] `FileSignature.Promised` and `JarState.Promised` should both have `get()` as their method. --- lib/src/main/java/com/diffplug/spotless/FileSignature.java | 6 +++--- lib/src/main/java/com/diffplug/spotless/JarState.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/FileSignature.java b/lib/src/main/java/com/diffplug/spotless/FileSignature.java index 9bbc62b9ec..e252232772 100644 --- a/lib/src/main/java/com/diffplug/spotless/FileSignature.java +++ b/lib/src/main/java/com/diffplug/spotless/FileSignature.java @@ -108,10 +108,10 @@ private Promised(List files, @Nullable FileSignature cached) { this.cached = cached; } - public FileSignature stripAbsolutePaths() throws IOException { + public FileSignature get() { if (cached == null) { // null when restored via serialization - cached = new FileSignature(files); + cached = ThrowingEx.get(() -> new FileSignature(files)); } return cached; } @@ -135,7 +135,7 @@ public Promised roundTrippable() { public static @Nullable FileSignature stripAbsolutePathsNullable(@Nullable Promised roundTrippable) throws IOException { if (roundTrippable != null) { - return roundTrippable.stripAbsolutePaths(); + return roundTrippable.get(); } else { return null; } diff --git a/lib/src/main/java/com/diffplug/spotless/JarState.java b/lib/src/main/java/com/diffplug/spotless/JarState.java index 659894a40a..73e5167fc1 100644 --- a/lib/src/main/java/com/diffplug/spotless/JarState.java +++ b/lib/src/main/java/com/diffplug/spotless/JarState.java @@ -54,7 +54,7 @@ public JarState get() { cached = result.fileSignature.roundTrippable(); return result; } - return new JarState(cached.stripAbsolutePaths()); + return new JarState(cached.get()); } catch (Exception e) { throw ThrowingEx.asRuntime(e); } From ab1452d82f11da3712d0ed8ec4aeb9f6d530c6c4 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 6 Dec 2023 00:10:02 -0800 Subject: [PATCH 16/23] Streamline the `FileSignature` api. --- .../com/diffplug/spotless/FileSignature.java | 18 +----------------- .../java/com/diffplug/spotless/JarState.java | 2 +- .../diffplug/spotless/kotlin/DiktatStep.java | 4 ++-- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/FileSignature.java b/lib/src/main/java/com/diffplug/spotless/FileSignature.java index e252232772..35949426ea 100644 --- a/lib/src/main/java/com/diffplug/spotless/FileSignature.java +++ b/lib/src/main/java/com/diffplug/spotless/FileSignature.java @@ -121,26 +121,10 @@ public static Promised promise(Iterable files) { return new Promised(MoreIterables.toNullHostileList(files), null); } - public Promised roundTrippable() { + public Promised asPromise() { return new Promised(files, this); } - public static @Nullable Promised roundTrippableNullable(@Nullable FileSignature signature) { - if (signature != null) { - return signature.roundTrippable(); - } else { - return null; - } - } - - public static @Nullable FileSignature stripAbsolutePathsNullable(@Nullable Promised roundTrippable) throws IOException { - if (roundTrippable != null) { - return roundTrippable.get(); - } else { - return null; - } - } - /** Returns all of the files in this signature, throwing an exception if there are more or less than 1 file. */ public Collection files() { return Collections.unmodifiableList(files); diff --git a/lib/src/main/java/com/diffplug/spotless/JarState.java b/lib/src/main/java/com/diffplug/spotless/JarState.java index 73e5167fc1..cdb8943877 100644 --- a/lib/src/main/java/com/diffplug/spotless/JarState.java +++ b/lib/src/main/java/com/diffplug/spotless/JarState.java @@ -51,7 +51,7 @@ public JarState get() { try { if (cached == null) { JarState result = supplier.get(); - cached = result.fileSignature.roundTrippable(); + cached = result.fileSignature.asPromise(); return result; } return new JarState(cached.get()); diff --git a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java index 9694033752..23d364502e 100644 --- a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java +++ b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java @@ -34,7 +34,7 @@ public class DiktatStep implements Serializable { private DiktatStep(JarState.Promised jarState, boolean isScript, @Nullable FileSignature config) { this.jarState = jarState; this.isScript = isScript; - this.config = FileSignature.roundTrippableNullable(config); + this.config = config != null ? config.asPromise() : null; } private static final String MIN_SUPPORTED_VERSION = "1.2.1"; @@ -72,7 +72,7 @@ public static FormatterStep create(String versionDiktat, Provisioner provisioner } private State equalityState() throws Exception { - return new State(jarState.get(), isScript, FileSignature.stripAbsolutePathsNullable(config)); + return new State(jarState.get(), isScript, config != null ? config.get() : null); } static final class State implements Serializable { From 4e54fc226410654880c461a4c293e9a9662d53fa Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 6 Dec 2023 12:52:52 -0800 Subject: [PATCH 17/23] Update the `CONTRIBUTING.md` for our new roundtrip serializable world. --- CONTRIBUTING.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 287d835bca..d9079d8fed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -95,10 +95,31 @@ Here's a checklist for creating a new step for Spotless: - [ ] Class name ends in Step, `SomeNewStep`. - [ ] Class has a public static method named `create` that returns a `FormatterStep`. -- [ ] Has a test class named `SomeNewStepTest`. +- [ ] Has a test class named `SomeNewStepTest` that uses `StepHarness` or `StepHarnessWithFile` to test the step. + - [ ] Start with `StepHarness.forStep(myStep).supportsRoundTrip(false)`, and then add round trip support as decribed in the next section. - [ ] Test class has test methods to verify behavior. - [ ] Test class has a test method `equality()` which tests equality using `StepEqualityTester` (see existing methods for examples). +### Serialization roundtrip + +In order to support Gradle's configuration cache, all `FormatterStep` must be round-trip serializable. This is a bit tricky because step equality is based on the serialied form of the state, and `transient` is used to take absolute paths out of the equality check. To make this work, roundtrip compatible steps actually have *two* states + +- `RoundtripState` which must be roundtrip serializable but has no equality constraints + - `FileSignature.Promised` for settings files and `JarState.Promised` for the classpath +- `EqualityState` which will never be reserialized and its serialized form is used for equality / hashCode checks + - `FileSignature` for settings files and `JarState` for the classpath + +```java +FormatterStep create(String name, + RoundtripState roundTrip, + SerializedFunction equalityFunc, + SerializedFunction formatterFunc) +FormatterStep createLazy(String name, + Supplier roundTrip, + SerializedFunction equalityFunc, + SerializedFunction formatterFunc) +``` + ### Third-party dependencies via reflection or compile-only source sets Most formatters are going to use some kind of third-party jar. Spotless integrates with many formatters, some of which have incompatible transitive dependencies. To address this, we resolve third-party dependencies using [`JarState`](https://github.com/diffplug/spotless/blob/b26f0972b185995d7c6a7aefa726c146d24d9a82/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java#L118). To call methods on the classes in that `JarState`, you can either use reflection or a compile-only source set. See [#524](https://github.com/diffplug/spotless/issues/524) for examples of both approaches. From 005113e78388919ef447f70a35652b203dc848ed Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 15 Dec 2023 15:44:44 -0800 Subject: [PATCH 18/23] Remove unneeded `public` declarations. --- .../com/diffplug/spotless/FormatterStep.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java index fdaa9cc068..13076e8744 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java @@ -29,7 +29,7 @@ */ public interface FormatterStep extends Serializable { /** The name of the step, for debugging purposes. */ - public String getName(); + String getName(); /** * Returns a formatted version of the given content. @@ -43,7 +43,8 @@ public interface FormatterStep extends Serializable { * if the formatter step doesn't have any changes to make * @throws Exception if the formatter step experiences a problem */ - public @Nullable String format(String rawUnix, File file) throws Exception; + @Nullable + String format(String rawUnix, File file) throws Exception; /** * Returns a new FormatterStep which will only apply its changes @@ -54,7 +55,7 @@ public interface FormatterStep extends Serializable { * @return FormatterStep */ @Deprecated - public default FormatterStep filterByContentPattern(String contentPattern) { + default FormatterStep filterByContentPattern(String contentPattern) { return filterByContent(OnMatch.INCLUDE, contentPattern); } @@ -68,7 +69,7 @@ public default FormatterStep filterByContentPattern(String contentPattern) { * java regular expression used to filter in or out files which content contain pattern * @return FormatterStep */ - public default FormatterStep filterByContent(OnMatch onMatch, String contentPattern) { + default FormatterStep filterByContent(OnMatch onMatch, String contentPattern) { return new FilterByContentPatternFormatterStep(this, onMatch, contentPattern); } @@ -78,7 +79,7 @@ public default FormatterStep filterByContent(OnMatch onMatch, String contentPatt *

* The provided filter must be serializable. */ - public default FormatterStep filterByFile(SerializableFileFilter filter) { + default FormatterStep filterByFile(SerializableFileFilter filter) { return new FilterByFileFormatterStep(this, filter); } @@ -124,7 +125,7 @@ static ThrowingEx.Supplier roundtripInit, SerializedFunction equalityFunc, SerializedFunction formatterFunc) { - return new FormatterStepSerializationRoundtrip(name, roundtripInit, equalityFunc, formatterFunc); + return new FormatterStepSerializationRoundtrip<>(name, roundtripInit, equalityFunc, formatterFunc); } /** @@ -160,7 +161,7 @@ static * only the state supplied by state and nowhere else. * @return A FormatterStep */ - public static FormatterStep createLazy( + static FormatterStep createLazy( String name, ThrowingEx.Supplier stateSupplier, ThrowingEx.Function stateToFormatter) { @@ -177,7 +178,7 @@ public static FormatterStep createLazy( * only the state supplied by state and nowhere else. * @return A FormatterStep */ - public static FormatterStep create( + static FormatterStep create( String name, State state, ThrowingEx.Function stateToFormatter) { @@ -194,7 +195,7 @@ public static FormatterStep create( * @return A FormatterStep which will never report that it is up-to-date, because * it is not equal to the serialized representation of itself. */ - public static FormatterStep createNeverUpToDateLazy( + static FormatterStep createNeverUpToDateLazy( String name, ThrowingEx.Supplier functionSupplier) { return new FormatterStepImpl.NeverUpToDate(name, functionSupplier); @@ -208,7 +209,7 @@ public static FormatterStep createNeverUpToDateLazy( * @return A FormatterStep which will never report that it is up-to-date, because * it is not equal to the serialized representation of itself. */ - public static FormatterStep createNeverUpToDate( + static FormatterStep createNeverUpToDate( String name, FormatterFunc function) { Objects.requireNonNull(function, "function"); From ce34f8958667ba39350073ed4fa811d7e507832a Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 22 Jan 2024 22:49:57 -0800 Subject: [PATCH 19/23] Fix various typos found by @jbduncan --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9079d8fed..0e33516840 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,13 +96,13 @@ Here's a checklist for creating a new step for Spotless: - [ ] Class name ends in Step, `SomeNewStep`. - [ ] Class has a public static method named `create` that returns a `FormatterStep`. - [ ] Has a test class named `SomeNewStepTest` that uses `StepHarness` or `StepHarnessWithFile` to test the step. - - [ ] Start with `StepHarness.forStep(myStep).supportsRoundTrip(false)`, and then add round trip support as decribed in the next section. + - [ ] Start with `StepHarness.forStep(myStep).supportsRoundTrip(false)`, and then add round trip support as described in the next section. - [ ] Test class has test methods to verify behavior. - [ ] Test class has a test method `equality()` which tests equality using `StepEqualityTester` (see existing methods for examples). ### Serialization roundtrip -In order to support Gradle's configuration cache, all `FormatterStep` must be round-trip serializable. This is a bit tricky because step equality is based on the serialied form of the state, and `transient` is used to take absolute paths out of the equality check. To make this work, roundtrip compatible steps actually have *two* states +In order to support Gradle's configuration cache, all `FormatterStep` must be round-trip serializable. This is a bit tricky because step equality is based on the serialized form of the state, and `transient` is used to take absolute paths out of the equality check. To make this work, roundtrip compatible steps actually have *two* states: - `RoundtripState` which must be roundtrip serializable but has no equality constraints - `FileSignature.Promised` for settings files and `JarState.Promised` for the classpath From 3bd42431e1c8b820f24474c471ff69eed8e800a9 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 22 Jan 2024 22:59:32 -0800 Subject: [PATCH 20/23] Copyright updates. --- .../com/diffplug/spotless/extra/EclipseBasedStepBuilder.java | 2 +- lib/src/main/java/com/diffplug/spotless/FileSignature.java | 2 +- lib/src/main/java/com/diffplug/spotless/Formatter.java | 2 +- lib/src/main/java/com/diffplug/spotless/FormatterStep.java | 2 +- .../spotless/FormatterStepEqualityOnStateSerialization.java | 2 +- .../diffplug/spotless/FormatterStepSerializationRoundtrip.java | 2 +- lib/src/main/java/com/diffplug/spotless/JarState.java | 2 +- lib/src/main/java/com/diffplug/spotless/SerializedFunction.java | 2 +- lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java | 2 +- .../java/com/diffplug/spotless/SerializableEqualityTester.java | 2 +- testlib/src/main/java/com/diffplug/spotless/StepHarness.java | 2 +- .../src/main/java/com/diffplug/spotless/StepHarnessBase.java | 2 +- .../main/java/com/diffplug/spotless/StepHarnessWithFile.java | 2 +- .../com/diffplug/spotless/generic/LicenseHeaderStepTest.java | 2 +- .../java/com/diffplug/spotless/npm/EslintFormatterStepTest.java | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/EclipseBasedStepBuilder.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/EclipseBasedStepBuilder.java index 0cf14eb32c..f22d0a6806 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/EclipseBasedStepBuilder.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/EclipseBasedStepBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/src/main/java/com/diffplug/spotless/FileSignature.java b/lib/src/main/java/com/diffplug/spotless/FileSignature.java index 35949426ea..6896d99167 100644 --- a/lib/src/main/java/com/diffplug/spotless/FileSignature.java +++ b/lib/src/main/java/com/diffplug/spotless/FileSignature.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/src/main/java/com/diffplug/spotless/Formatter.java b/lib/src/main/java/com/diffplug/spotless/Formatter.java index 5d2ca8369e..fc6fe32afd 100644 --- a/lib/src/main/java/com/diffplug/spotless/Formatter.java +++ b/lib/src/main/java/com/diffplug/spotless/Formatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java index 13076e8744..26bc1e56e5 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStepEqualityOnStateSerialization.java b/lib/src/main/java/com/diffplug/spotless/FormatterStepEqualityOnStateSerialization.java index ca7fa7cfc0..0ff279c5f5 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStepEqualityOnStateSerialization.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStepEqualityOnStateSerialization.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java b/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java index d996c0b360..3af89083fc 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 DiffPlug + * Copyright 2023-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/src/main/java/com/diffplug/spotless/JarState.java b/lib/src/main/java/com/diffplug/spotless/JarState.java index cdb8943877..8680932b9e 100644 --- a/lib/src/main/java/com/diffplug/spotless/JarState.java +++ b/lib/src/main/java/com/diffplug/spotless/JarState.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/src/main/java/com/diffplug/spotless/SerializedFunction.java b/lib/src/main/java/com/diffplug/spotless/SerializedFunction.java index 56f5974870..d4d36edfb4 100644 --- a/lib/src/main/java/com/diffplug/spotless/SerializedFunction.java +++ b/lib/src/main/java/com/diffplug/spotless/SerializedFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 DiffPlug + * Copyright 2023-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java b/lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java index 5eb7c0f758..91155b1a79 100644 --- a/lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java +++ b/lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/testlib/src/main/java/com/diffplug/spotless/SerializableEqualityTester.java b/testlib/src/main/java/com/diffplug/spotless/SerializableEqualityTester.java index 635f11f305..726597a323 100644 --- a/testlib/src/main/java/com/diffplug/spotless/SerializableEqualityTester.java +++ b/testlib/src/main/java/com/diffplug/spotless/SerializableEqualityTester.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/testlib/src/main/java/com/diffplug/spotless/StepHarness.java b/testlib/src/main/java/com/diffplug/spotless/StepHarness.java index 79c759770f..0f6ddc9ad9 100644 --- a/testlib/src/main/java/com/diffplug/spotless/StepHarness.java +++ b/testlib/src/main/java/com/diffplug/spotless/StepHarness.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java b/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java index 0a0154176b..8624972c4e 100644 --- a/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java +++ b/testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 DiffPlug + * Copyright 2023-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java b/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java index a557b76017..ae2f22db1e 100644 --- a/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java +++ b/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java b/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java index a9ec563e58..b5e08d2232 100644 --- a/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/testlib/src/test/java/com/diffplug/spotless/npm/EslintFormatterStepTest.java b/testlib/src/test/java/com/diffplug/spotless/npm/EslintFormatterStepTest.java index 1b365e8d11..ab54b11bee 100644 --- a/testlib/src/test/java/com/diffplug/spotless/npm/EslintFormatterStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/npm/EslintFormatterStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 4fdb178503728aa1bc2c49a26f87e8e45a6230a7 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 23 Jan 2024 13:52:00 -0800 Subject: [PATCH 21/23] Remove unused method. --- lib/src/main/java/com/diffplug/spotless/FileSignature.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/FileSignature.java b/lib/src/main/java/com/diffplug/spotless/FileSignature.java index 6896d99167..d6a23a8fd1 100644 --- a/lib/src/main/java/com/diffplug/spotless/FileSignature.java +++ b/lib/src/main/java/com/diffplug/spotless/FileSignature.java @@ -117,10 +117,6 @@ public FileSignature get() { } } - public static Promised promise(Iterable files) { - return new Promised(MoreIterables.toNullHostileList(files), null); - } - public Promised asPromise() { return new Promised(files, this); } From 14991e4ab61ce757c70f996605205f4487652035 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 23 Jan 2024 13:52:07 -0800 Subject: [PATCH 22/23] Update changelog. --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index adf6d109e7..c6c9f29430 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,10 @@ This document is intended for Spotless developers. We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`). ## [Unreleased] +### Added +* `FileSignature.Promised` and `JarState.Promised` to facilitate round-trip serialization for the Gradle configuration cache. ([#1945](https://github.com/diffplug/spotless/pull/1945)) +### Removed +* **BREAKING** Remove `JarState.getMavenCoordinate(String prefix)`. ([#1945](https://github.com/diffplug/spotless/pull/1945)) ## [2.45.0] - 2024-01-23 ### Added From 4b15b3930b15b7df7af279d9a2c2c59e2ea53c46 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 23 Jan 2024 14:15:14 -0800 Subject: [PATCH 23/23] Fix an oversight from the merge conflict earlier. --- .../java/com/diffplug/spotless/kotlin/DiktatStep.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java index b3cfa88c1a..425786d64e 100644 --- a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java +++ b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java @@ -71,8 +71,14 @@ public static FormatterStep create(String versionDiktat, Provisioner provisioner } Objects.requireNonNull(versionDiktat, "versionDiktat"); Objects.requireNonNull(provisioner, "provisioner"); + final String diktatCoordinate; + if (BadSemver.version(versionDiktat) >= BadSemver.version(PACKAGE_RELOCATED_VERSION)) { + diktatCoordinate = MAVEN_COORDINATE + versionDiktat; + } else { + diktatCoordinate = MAVEN_COORDINATE_PRE_2_0_0 + versionDiktat; + } return FormatterStep.create(NAME, - new DiktatStep(JarState.promise(() -> JarState.from(MAVEN_COORDINATE + versionDiktat, provisioner)), versionDiktat, isScript, config), + new DiktatStep(JarState.promise(() -> JarState.from(diktatCoordinate, provisioner)), versionDiktat, isScript, config), DiktatStep::equalityState, State::createFormat); }