Skip to content

Commit 1ad019d

Browse files
authored
StepHarness now round-trips everything (#2085)
2 parents 268b6f6 + 9f016c8 commit 1ad019d

File tree

7 files changed

+99
-81
lines changed

7 files changed

+99
-81
lines changed

lib/src/main/java/com/diffplug/spotless/Formatter.java

+7
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535

3636
import javax.annotation.Nullable;
3737

38+
import com.diffplug.spotless.generic.FenceStep;
39+
3840
/** Formatter which performs the full formatting. */
3941
public final class Formatter implements Serializable, AutoCloseable {
4042
private static final long serialVersionUID = 1L;
@@ -302,10 +304,15 @@ public boolean equals(Object obj) {
302304
@Override
303305
public void close() {
304306
for (FormatterStep step : steps) {
307+
if (step instanceof DelegateFormatterStep) {
308+
step = ((DelegateFormatterStep) step).delegateStep;
309+
}
305310
if (step instanceof FormatterStepImpl.Standard) {
306311
((FormatterStepImpl.Standard) step).cleanupFormatterFunc();
307312
} else if (step instanceof FormatterStepEqualityOnStateSerialization) {
308313
((FormatterStepEqualityOnStateSerialization) step).cleanupFormatterFunc();
314+
} else if (step instanceof FenceStep.BaseStep) {
315+
((FenceStep.BaseStep) step).cleanup();
309316
}
310317
}
311318
}

lib/src/main/java/com/diffplug/spotless/generic/FenceStep.java

+52-17
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@
2525
import java.util.regex.Matcher;
2626
import java.util.regex.Pattern;
2727

28+
import javax.annotation.Nullable;
29+
2830
import com.diffplug.spotless.Formatter;
2931
import com.diffplug.spotless.FormatterFunc;
3032
import com.diffplug.spotless.FormatterStep;
3133
import com.diffplug.spotless.LineEnding;
32-
import com.diffplug.spotless.SerializedFunction;
3334

3435
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
3536

@@ -81,10 +82,7 @@ private void assertRegexSet() {
8182
/** Returns a step which will apply the given steps but preserve the content selected by the regex / openClose pair. */
8283
public FormatterStep preserveWithin(List<FormatterStep> steps) {
8384
assertRegexSet();
84-
return FormatterStep.createLazy(name,
85-
() -> new PreserveWithin(regex, steps),
86-
SerializedFunction.identity(),
87-
state -> FormatterFunc.Closeable.of(state.buildFormatter(), state));
85+
return new PreserveWithin(name, regex, steps);
8886
}
8987

9088
/**
@@ -93,17 +91,14 @@ public FormatterStep preserveWithin(List<FormatterStep> steps) {
9391
*/
9492
public FormatterStep applyWithin(List<FormatterStep> steps) {
9593
assertRegexSet();
96-
return FormatterStep.createLazy(name,
97-
() -> new ApplyWithin(regex, steps),
98-
SerializedFunction.identity(),
99-
state -> FormatterFunc.Closeable.of(state.buildFormatter(), state));
94+
return new ApplyWithin(name, regex, steps);
10095
}
10196

102-
static class ApplyWithin extends Apply implements FormatterFunc.Closeable.ResourceFuncNeedsFile<Formatter> {
97+
static class ApplyWithin extends BaseStep {
10398
private static final long serialVersionUID = 17061466531957339L;
10499

105-
ApplyWithin(Pattern regex, List<FormatterStep> steps) {
106-
super(regex, steps);
100+
ApplyWithin(String name, Pattern regex, List<FormatterStep> steps) {
101+
super(name, regex, steps);
107102
}
108103

109104
@Override
@@ -119,11 +114,11 @@ public String apply(Formatter formatter, String unix, File file) throws Exceptio
119114
}
120115
}
121116

122-
static class PreserveWithin extends Apply implements FormatterFunc.Closeable.ResourceFuncNeedsFile<Formatter> {
117+
static class PreserveWithin extends BaseStep {
123118
private static final long serialVersionUID = -8676786492305178343L;
124119

125-
PreserveWithin(Pattern regex, List<FormatterStep> steps) {
126-
super(regex, steps);
120+
PreserveWithin(String name, Pattern regex, List<FormatterStep> steps) {
121+
super(name, regex, steps);
127122
}
128123

129124
private void storeGroups(String unix) {
@@ -144,15 +139,17 @@ public String apply(Formatter formatter, String unix, File file) throws Exceptio
144139
}
145140

146141
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
147-
static class Apply implements Serializable {
142+
public static abstract class BaseStep implements Serializable, FormatterStep, FormatterFunc.Closeable.ResourceFuncNeedsFile<Formatter> {
143+
final String name;
148144
private static final long serialVersionUID = -2301848328356559915L;
149145
final Pattern regex;
150146
final List<FormatterStep> steps;
151147

152148
transient ArrayList<String> groups = new ArrayList<>();
153149
transient StringBuilder builderInternal;
154150

155-
public Apply(Pattern regex, List<FormatterStep> steps) {
151+
public BaseStep(String name, Pattern regex, List<FormatterStep> steps) {
152+
this.name = name;
156153
this.regex = regex;
157154
this.steps = steps;
158155
}
@@ -218,5 +215,43 @@ protected String assembleGroups(String unix) {
218215
throw new Error("An intermediate step removed a match of " + pattern);
219216
}
220217
}
218+
219+
@Override
220+
public String getName() {
221+
return name;
222+
}
223+
224+
private transient Formatter formatter;
225+
226+
@Nullable
227+
@Override
228+
public String format(String rawUnix, File file) throws Exception {
229+
if (formatter == null) {
230+
formatter = buildFormatter();
231+
}
232+
return this.apply(formatter, rawUnix, file);
233+
}
234+
235+
@Override
236+
public boolean equals(Object o) {
237+
if (this == o)
238+
return true;
239+
if (o == null || getClass() != o.getClass())
240+
return false;
241+
BaseStep step = (BaseStep) o;
242+
return name.equals(step.name) && regex.pattern().equals(step.regex.pattern()) && regex.flags() == step.regex.flags() && steps.equals(step.steps);
243+
}
244+
245+
@Override
246+
public int hashCode() {
247+
return Objects.hash(name, regex.pattern(), regex.flags(), steps);
248+
}
249+
250+
public void cleanup() {
251+
if (formatter != null) {
252+
formatter.close();
253+
formatter = null;
254+
}
255+
}
221256
}
222257
}

testlib/src/main/java/com/diffplug/spotless/StepHarness.java

+14-4
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
import org.assertj.core.api.Assertions;
2727

2828
/** 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. */
29-
public class StepHarness extends StepHarnessBase<StepHarness> {
30-
private StepHarness(Formatter formatter) {
31-
super(formatter);
29+
public class StepHarness extends StepHarnessBase {
30+
private StepHarness(Formatter formatter, RoundTrip roundTrip) {
31+
super(formatter, roundTrip);
3232
}
3333

3434
/** Creates a harness for testing steps which don't depend on the file. */
@@ -49,7 +49,17 @@ public static StepHarness forSteps(FormatterStep... steps) {
4949

5050
/** Creates a harness for testing a formatter whose steps don't depend on the file. */
5151
public static StepHarness forFormatter(Formatter formatter) {
52-
return new StepHarness(formatter);
52+
return new StepHarness(formatter, RoundTrip.ASSERT_EQUAL);
53+
}
54+
55+
public static StepHarness forStepNoRoundtrip(FormatterStep step) {
56+
return new StepHarness(Formatter.builder()
57+
.steps(Arrays.asList(step))
58+
.lineEndingsPolicy(LineEnding.UNIX.createPolicy())
59+
.encoding(StandardCharsets.UTF_8)
60+
.rootDir(Paths.get(""))
61+
.exceptionPolicy(new FormatExceptionPolicyStrict())
62+
.build(), RoundTrip.DONT_ROUNDTRIP);
5363
}
5464

5565
/** Asserts that the given element is transformed as expected, and that the result is idempotent. */

testlib/src/main/java/com/diffplug/spotless/StepHarnessBase.java

+13-46
Original file line numberDiff line numberDiff line change
@@ -15,67 +15,34 @@
1515
*/
1616
package com.diffplug.spotless;
1717

18-
import java.util.Locale;
1918
import java.util.Objects;
20-
import java.util.Set;
2119

2220
import org.assertj.core.api.Assertions;
2321

24-
class StepHarnessBase<T extends StepHarnessBase<?>> implements AutoCloseable {
22+
class StepHarnessBase implements AutoCloseable {
23+
enum RoundTrip {
24+
ASSERT_EQUAL, DONT_ASSERT_EQUAL, DONT_ROUNDTRIP
25+
}
26+
2527
private final Formatter formatter;
26-
private Formatter roundTripped;
27-
private boolean supportsRoundTrip = false;
2828

29-
protected StepHarnessBase(Formatter formatter) {
29+
protected StepHarnessBase(Formatter formatter, RoundTrip roundTrip) {
3030
this.formatter = Objects.requireNonNull(formatter);
31-
if (formatter.getSteps().size() == 1) {
32-
// our goal is for everything to be roundtrip serializable
33-
// the steps to get there are
34-
// - make every individual step round-trippable
35-
// - make the other machinery (Formatter, LineEnding, etc) round-trippable
36-
// - done!
37-
//
38-
// Right now, we're still trying to get each and every single step to be round-trippable.
39-
// You can help by add a test below, make sure that the test for that step fails, and then
40-
// make the test pass. `FormatterStepEqualityOnStateSerialization` is a good base class for
41-
// easily converting a step to round-trip serialization while maintaining easy and concise
42-
// equality code.
43-
String onlyStepName = formatter.getSteps().get(0).getName();
44-
if (onlyStepName.startsWith("indentWith")) {
45-
supportsRoundTrip = true;
46-
} else if (onlyStepName.equals("diktat")) {
47-
supportsRoundTrip = true;
48-
} else if (onlyStepName.toLowerCase(Locale.ROOT).contains("eclipse")) {
49-
supportsRoundTrip = true;
50-
} else if (onlyStepName.equals("fence")) {
51-
supportsRoundTrip = true;
52-
} else if (Set.of("black", "buf", "clang", "ktlint", "ktfmt", "scalafmt", "palantir-java-format", "google-java-format",
53-
"removeUnusedImports", "cleanthat", "No line break between type annotation and type", "antlr4Formatter",
54-
"gson", "jacksonJson", "apply-json-patch", "jsonSimple", "sortPom", "jacksonYaml", "gherkinUtils",
55-
"flexmark-java", "freshmark",
56-
"importOrder", "Remove unnecessary semicolons").contains(onlyStepName)) {
57-
supportsRoundTrip = true;
58-
}
31+
if (roundTrip == RoundTrip.DONT_ROUNDTRIP) {
32+
return;
33+
}
34+
Formatter roundTripped = SerializableEqualityTester.reserialize(formatter);
35+
if (roundTrip == RoundTrip.ASSERT_EQUAL) {
36+
Assertions.assertThat(roundTripped).isEqualTo(formatter);
5937
}
6038
}
6139

6240
protected Formatter formatter() {
63-
if (!supportsRoundTrip) {
64-
return formatter;
65-
} else {
66-
if (roundTripped == null) {
67-
roundTripped = SerializableEqualityTester.reserialize(formatter);
68-
Assertions.assertThat(roundTripped).isEqualTo(formatter);
69-
}
70-
return roundTripped;
71-
}
41+
return formatter;
7242
}
7343

7444
@Override
7545
public void close() {
7646
formatter.close();
77-
if (roundTripped != null) {
78-
roundTripped.close();
79-
}
8047
}
8148
}

testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@
2626
import org.assertj.core.api.Assertions;
2727

2828
/** An api for testing a {@code FormatterStep} that depends on the File path. */
29-
public class StepHarnessWithFile extends StepHarnessBase<StepHarnessWithFile> {
29+
public class StepHarnessWithFile extends StepHarnessBase {
3030
private final ResourceHarness harness;
3131

32-
private StepHarnessWithFile(ResourceHarness harness, Formatter formatter) {
33-
super(formatter);
32+
private StepHarnessWithFile(ResourceHarness harness, Formatter formatter, RoundTrip roundTrip) {
33+
super(formatter, roundTrip);
3434
this.harness = Objects.requireNonNull(harness);
3535
}
3636

3737
/** Creates a harness for testing steps which do depend on the file. */
3838
public static StepHarnessWithFile forStep(ResourceHarness harness, FormatterStep step) {
39-
return new StepHarnessWithFile(harness, Formatter.builder()
39+
return forFormatter(harness, Formatter.builder()
4040
.name(step.getName())
4141
.encoding(StandardCharsets.UTF_8)
4242
.lineEndingsPolicy(LineEnding.UNIX.createPolicy())
@@ -48,7 +48,7 @@ public static StepHarnessWithFile forStep(ResourceHarness harness, FormatterStep
4848

4949
/** Creates a harness for testing a formatter whose steps do depend on the file. */
5050
public static StepHarnessWithFile forFormatter(ResourceHarness harness, Formatter formatter) {
51-
return new StepHarnessWithFile(harness, formatter);
51+
return new StepHarnessWithFile(harness, formatter, RoundTrip.ASSERT_EQUAL);
5252
}
5353

5454
/** Asserts that the given element is transformed as expected, and that the result is idempotent. */

testlib/src/test/java/com/diffplug/spotless/generic/FenceStepTest.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,13 @@
2929
import com.diffplug.spotless.FormatterStep;
3030
import com.diffplug.spotless.ResourceHarness;
3131
import com.diffplug.spotless.StepHarness;
32-
import com.diffplug.spotless.StepHarnessWithFile;
3332

3433
class FenceStepTest extends ResourceHarness {
3534
@Test
3635
void single() {
3736
FormatterStep fence = FenceStep.named("fence").openClose("spotless:off", "spotless:on")
3837
.preserveWithin(Arrays.asList(createNeverUpToDateSerializable("lowercase", String::toLowerCase)));
39-
StepHarness harness = StepHarness.forSteps(fence);
38+
StepHarness harness = StepHarness.forStepNoRoundtrip(fence);
4039
harness.test(
4140
StringPrinter.buildStringFromLines(
4241
"A B C",
@@ -56,7 +55,7 @@ void single() {
5655
void multiple() {
5756
FormatterStep fence = FenceStep.named("fence").openClose("spotless:off", "spotless:on")
5857
.preserveWithin(Arrays.asList(createNeverUpToDateSerializable("lowercase", String::toLowerCase)));
59-
StepHarness harness = StepHarness.forSteps(fence);
58+
StepHarness harness = StepHarness.forStepNoRoundtrip(fence);
6059
harness.test(
6160
StringPrinter.buildStringFromLines(
6261
"A B C",
@@ -90,9 +89,8 @@ void multiple() {
9089
void broken() {
9190
FormatterStep fence = FenceStep.named("fence").openClose("spotless:off", "spotless:on")
9291
.preserveWithin(Arrays.asList(createNeverUpToDateSerializable("uppercase", String::toUpperCase)));
93-
StepHarnessWithFile harness = StepHarnessWithFile.forStep(this, fence);
9492
// this fails because uppercase turns spotless:off into SPOTLESS:OFF, etc
95-
harness.testExceptionMsg(newFile("test"), StringPrinter.buildStringFromLines("A B C",
93+
StepHarness.forStepNoRoundtrip(fence).testExceptionMsg(StringPrinter.buildStringFromLines("A B C",
9694
"spotless:off",
9795
"D E F",
9896
"spotless:on",
@@ -103,7 +101,7 @@ void broken() {
103101
void andApply() {
104102
FormatterStep fence = FenceStep.named("fence").openClose("<lower>", "</lower>")
105103
.applyWithin(Arrays.asList(createNeverUpToDateSerializable("lowercase", String::toLowerCase)));
106-
StepHarness.forSteps(fence).test(
104+
StepHarness.forStepNoRoundtrip(fence).test(
107105
StringPrinter.buildStringFromLines(
108106
"A B C",
109107
"<lower>",
@@ -152,5 +150,6 @@ public String getName() {
152150
public String format(String rawUnix, File file) throws Exception {
153151
return formatterFunc.apply(rawUnix, file);
154152
}
153+
155154
}
156155
}

testlib/src/test/java/com/diffplug/spotless/java/GoogleJavaFormatStepTest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2023 DiffPlug
2+
* Copyright 2016-2024 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -64,7 +64,7 @@ void behavior() throws Exception {
6464
@Test
6565
void versionBelowMinimumRequiredVersionIsNotAllowed() throws Exception {
6666
FormatterStep step = GoogleJavaFormatStep.create("1.2", "AOSP", TestProvisioner.mavenCentral());
67-
StepHarness.forStep(step)
67+
StepHarness.forStepNoRoundtrip(step)
6868
.testResourceExceptionMsg("java/googlejavaformat/JavaCodeWithLicenseUnformatted.test")
6969
.contains("you are using 1.2");
7070
}
@@ -73,7 +73,7 @@ void versionBelowMinimumRequiredVersionIsNotAllowed() throws Exception {
7373
@EnabledForJreRange(min = JAVA_16)
7474
void versionBelowOneDotTenIsNotAllowed() throws Exception {
7575
FormatterStep step = GoogleJavaFormatStep.create("1.9", "AOSP", TestProvisioner.mavenCentral());
76-
StepHarness.forStep(step)
76+
StepHarness.forStepNoRoundtrip(step)
7777
.testResourceExceptionMsg("java/googlejavaformat/JavaCodeWithLicenseUnformatted.test")
7878
.contains("you are using 1.9");
7979
}

0 commit comments

Comments
 (0)