diff --git a/buildSrc/src/main/groovy/open-java-format.error-prone-conventions.gradle b/buildSrc/src/main/groovy/open-java-format.error-prone-conventions.gradle index a573c98b7..9bb1278d3 100644 --- a/buildSrc/src/main/groovy/open-java-format.error-prone-conventions.gradle +++ b/buildSrc/src/main/groovy/open-java-format.error-prone-conventions.gradle @@ -16,7 +16,7 @@ nullaway { tasks.withType(JavaCompile).configureEach { options.errorprone { - // Annotation processor output (derive4j, Immutables) is not ours to fix. + // Annotation processor output (Immutables) is not ours to fix. excludedPaths = '.*/build/generated/.*' } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 253826bfa..4d440a4fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,8 +26,6 @@ compileTesting = { module = "com.google.testing.compile:compile-testing", versio errorprone-core = { module = "com.google.errorprone:error_prone_core", version = "2.50.0" } errorProneSupport-contrib = { module = "tech.picnic.error-prone-support:error-prone-contrib", version = "0.30.0" } nullaway = { module = "com.uber.nullaway:nullaway", version = "0.14.2" } -derive4j = { module = "org.derive4j:derive4j", version = "1.1.1" } -derive4j-annotation = { module = "org.derive4j:derive4j-annotation", version = "1.1.1" } functionaljava = { module = "org.functionaljava:functionaljava", version = "5.0" } guava = { module = "com.google.guava:guava", version = "33.7.1-jre" } guava-testlib = { module = "com.google.guava:guava-testlib", version = "33.5.0-jre" } diff --git a/open-java-format/build.gradle b/open-java-format/build.gradle index 2f07d2c6d..b16e08fac 100644 --- a/open-java-format/build.gradle +++ b/open-java-format/build.gradle @@ -26,8 +26,6 @@ dependencies { compileOnly variantOf(libs.immutables.value) { classifier('annotations') } annotationProcessor libs.immutables.value - compileOnly libs.derive4j.annotation - annotationProcessor libs.derive4j implementation libs.jsr305 } diff --git a/open-java-format/src/main/java/com/palantir/javaformat/BreakBehaviour.java b/open-java-format/src/main/java/com/palantir/javaformat/BreakBehaviour.java index d2ba639bb..fa2c1b15b 100644 --- a/open-java-format/src/main/java/com/palantir/javaformat/BreakBehaviour.java +++ b/open-java-format/src/main/java/com/palantir/javaformat/BreakBehaviour.java @@ -23,96 +23,99 @@ import com.palantir.javaformat.doc.Doc; import com.palantir.javaformat.doc.Level; import java.io.IOException; -import java.io.UncheckedIOException; -import org.derive4j.ArgOption; -import org.derive4j.Data; -@Data(arguments = ArgOption.checkedNotNull) +/** + * How a {@link Level} is broken when it does not fit on one line. Each case's {@code toString()} is what + * {@link com.palantir.javaformat.doc.LevelDelimitedFlatValueDocVisitor} prints for it. + */ @Immutable @JsonSerialize(using = BreakBehaviour.Json.class) -public abstract class BreakBehaviour { - public interface Cases { +public sealed interface BreakBehaviour { - R breakThisLevel(); + static BreakBehaviour breakThisLevel() { + return new BreakThisLevel(); + } - /** - * If the last level is breakable, prefer breaking it if it will keep the rest of this level on line line. - * - * @param keepIndentWhenInlined whether to keep this level's indent when inlined as a recursive level (when - * reached via a previous `preferBreakingLastInnerLevel` whose breakability was - * {@link LastLevelBreakability#CHECK_INNER}) - */ - R preferBreakingLastInnerLevel(boolean keepIndentWhenInlined); + static BreakBehaviour preferBreakingLastInnerLevel(boolean keepIndentWhenInlined) { + return new PreferBreakingLastInnerLevel(keepIndentWhenInlined); + } - /** - * Attempt to inline the suffix of this level (which must be a {@link Level} and the last doc), recursing into - * the {@link Level} just before the last {@link Level} (if there is such a level) to see if that can be broken - * instead. - * - *

This behaves like {@link #breakThisLevel()} if we couldn't recurse into such an inner level, or if the - * suffix level doesn't fit on the last line. - */ - R inlineSuffix(); + static BreakBehaviour inlineSuffix() { + return new InlineSuffix(); + } - /** - * Break if by doing so all inner levels then fit on a single line. However, don't break if we can fit in the - * {@link Doc docs} up to the first break (which might be nested inside the next doc if it's a {@link Level}), - * in order to prevent exceeding the maxLength accidentally. - */ - R breakOnlyIfInnerLevelsThenFitOnOneLine(boolean keepIndentWhenInlined); + static BreakBehaviour breakOnlyIfInnerLevelsThenFitOnOneLine(boolean keepIndentWhenInlined) { + return new BreakOnlyIfInnerLevelsThenFitOnOneLine(keepIndentWhenInlined); } - public abstract R match(Cases cases); + /** Break this level. */ + record BreakThisLevel() implements BreakBehaviour { + @Override + public String toString() { + return "breakThisLevel()"; + } + } - /** For {@link com.palantir.javaformat.doc.LevelDelimitedFlatValueDocVisitor}. */ - @Override - public abstract String toString(); + /** + * If the last level is breakable, prefer breaking it if it will keep the rest of this level on line line. + * + * @param keepIndentWhenInlined whether to keep this level's indent when inlined as a recursive level (when + * reached via a previous `preferBreakingLastInnerLevel` whose breakability was + * {@link LastLevelBreakability#CHECK_INNER}) + */ + record PreferBreakingLastInnerLevel(boolean keepIndentWhenInlined) implements BreakBehaviour { + @Override + public String toString() { + return "preferBreakingLastInnerLevel(" + keepIndentWhenInlined + ")"; + } + } + + /** + * Attempt to inline the suffix of this level (which must be a {@link Level} and the last doc), recursing into the + * {@link Level} just before the last {@link Level} (if there is such a level) to see if that can be broken instead. + * + *

This behaves like {@link BreakThisLevel} if we couldn't recurse into such an inner level, or if the suffix + * level doesn't fit on the last line. + */ + record InlineSuffix() implements BreakBehaviour { + @Override + public String toString() { + return "inlineSuffix()"; + } + } /** - * This is gross but just wanted to get something working. See https://github.com/derive4j/derive4j/issues/51 for a - * potential better implementation. + * Break if by doing so all inner levels then fit on a single line. However, don't break if we can fit in the + * {@link Doc docs} up to the first break (which might be nested inside the next doc if it's a {@link Level}), in + * order to prevent exceeding the maxLength accidentally. + * + * @param keepIndentWhenInlined whether to keep this level's indent when inlined as a recursive level */ - static class Json extends JsonSerializer { + record BreakOnlyIfInnerLevelsThenFitOnOneLine(boolean keepIndentWhenInlined) implements BreakBehaviour { + @Override + public String toString() { + return "breakOnlyIfInnerLevelsThenFitOnOneLine(" + keepIndentWhenInlined + ")"; + } + } + /** Writes the case as {@code type} and its flag, if it has one. */ + final class Json extends JsonSerializer { @Override public void serialize(BreakBehaviour value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeStartObject(); - BreakBehaviours.caseOf(value) - .breakThisLevel(() -> { - try { - gen.writeObjectField("type", "breakThisLevel"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - return null; - }) - .preferBreakingLastInnerLevel(keepIndentWhenInlined -> { - try { - gen.writeObjectField("type", "preferBreakingLastInnerLevel"); - gen.writeObjectField("keepIndentWhenInlined", keepIndentWhenInlined); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - return null; - }) - .inlineSuffix(() -> { - try { - gen.writeObjectField("type", "inlineSuffix"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - return null; - }) - .breakOnlyIfInnerLevelsThenFitOnOneLine(keepIndentWhenInlined -> { - try { - gen.writeObjectField("type", "breakOnlyIfInnerLevelsThenFitOnOneLine"); - gen.writeObjectField("keepIndentWhenInlined", keepIndentWhenInlined); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - return null; - }); + switch (value) { + case BreakThisLevel() -> gen.writeObjectField("type", "breakThisLevel"); + case PreferBreakingLastInnerLevel(boolean keepIndentWhenInlined) -> { + gen.writeObjectField("type", "preferBreakingLastInnerLevel"); + gen.writeObjectField("keepIndentWhenInlined", keepIndentWhenInlined); + } + case InlineSuffix() -> gen.writeObjectField("type", "inlineSuffix"); + case BreakOnlyIfInnerLevelsThenFitOnOneLine(boolean keepIndentWhenInlined) -> { + gen.writeObjectField("type", "breakOnlyIfInnerLevelsThenFitOnOneLine"); + gen.writeObjectField("keepIndentWhenInlined", keepIndentWhenInlined); + } + } gen.writeEndObject(); } } diff --git a/open-java-format/src/main/java/com/palantir/javaformat/LastLevelBreakability.java b/open-java-format/src/main/java/com/palantir/javaformat/LastLevelBreakability.java index 9b96e53b8..f1e6c0000 100644 --- a/open-java-format/src/main/java/com/palantir/javaformat/LastLevelBreakability.java +++ b/open-java-format/src/main/java/com/palantir/javaformat/LastLevelBreakability.java @@ -20,25 +20,25 @@ /** * How to decide whether to break the last inner level ("this level") of a parent level with - * {@link BreakBehaviour.Cases#preferBreakingLastInnerLevel}. + * {@link BreakBehaviour.PreferBreakingLastInnerLevel}. */ public enum LastLevelBreakability { /** - * Default behaviour. When processing a {@link BreakBehaviour.Cases#preferBreakingLastInnerLevel} chain, if we've + * Default behaviour. When processing a {@link BreakBehaviour.PreferBreakingLastInnerLevel} chain, if we've * arrived at a level with this breakability, then we should abort the chain. */ ABORT, /** * Unconditionally allow ending an inline chain at this level, after which this level may be broken as usual, or a * prefix thereof could be inlined further (if it has the appropriate break behaviour of - * {@link BreakBehaviour.Cases#preferBreakingLastInnerLevel}). This should only be used when you know that the first + * {@link BreakBehaviour.PreferBreakingLastInnerLevel}). This should only be used when you know that the first * non-Level {@link Doc} inside this level, if you flatten it, is a {@link Break}. */ ACCEPT_INLINE_CHAIN, /** * Delegate to the {@link LastLevelBreakability} of _this_ level's last inner level. Typically, this will be true if * this level is not immediately followed by a break (see StartsWithBreakVisitor). Behaves the same as - * {@link #ABORT} if this level is not {@link BreakBehaviour.Cases#preferBreakingLastInnerLevel}. + * {@link #ABORT} if this level is not {@link BreakBehaviour.PreferBreakingLastInnerLevel}. */ CHECK_INNER, /** diff --git a/open-java-format/src/main/java/com/palantir/javaformat/OpenOp.java b/open-java-format/src/main/java/com/palantir/javaformat/OpenOp.java index ba25f077c..0f36e404c 100644 --- a/open-java-format/src/main/java/com/palantir/javaformat/OpenOp.java +++ b/open-java-format/src/main/java/com/palantir/javaformat/OpenOp.java @@ -41,7 +41,7 @@ public abstract class OpenOp extends HasUniqueId implements Op { */ @Default public BreakBehaviour breakBehaviour() { - return BreakBehaviours.breakThisLevel(); + return BreakBehaviour.breakThisLevel(); } /** If it's the last level of its parent, when to inline this level rather than break the parent. */ diff --git a/open-java-format/src/main/java/com/palantir/javaformat/PartialInlineability.java b/open-java-format/src/main/java/com/palantir/javaformat/PartialInlineability.java index 2d300047a..423c67527 100644 --- a/open-java-format/src/main/java/com/palantir/javaformat/PartialInlineability.java +++ b/open-java-format/src/main/java/com/palantir/javaformat/PartialInlineability.java @@ -20,7 +20,7 @@ /** * What are the conditions for a level to be partially inlineable. Partial inlining refers to the behaviour of - * {@link BreakBehaviours#breakOnlyIfInnerLevelsThenFitOnOneLine} where a level is too large to fit on the current line, + * {@link BreakBehaviour.BreakOnlyIfInnerLevelsThenFitOnOneLine} where a level is too large to fit on the current line, * but a prefix thereof is partially inlined onto the current line. * *

Specifically, when inlining a level with the above behaviour, the partial inlineability of its first diff --git a/open-java-format/src/main/java/com/palantir/javaformat/doc/Level.java b/open-java-format/src/main/java/com/palantir/javaformat/doc/Level.java index 4f613f9ed..af9bfcc32 100644 --- a/open-java-format/src/main/java/com/palantir/javaformat/doc/Level.java +++ b/open-java-format/src/main/java/com/palantir/javaformat/doc/Level.java @@ -24,7 +24,10 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Range; import com.palantir.javaformat.BreakBehaviour; -import com.palantir.javaformat.BreakBehaviours; +import com.palantir.javaformat.BreakBehaviour.BreakOnlyIfInnerLevelsThenFitOnOneLine; +import com.palantir.javaformat.BreakBehaviour.BreakThisLevel; +import com.palantir.javaformat.BreakBehaviour.InlineSuffix; +import com.palantir.javaformat.BreakBehaviour.PreferBreakingLastInnerLevel; import com.palantir.javaformat.CommentsHelper; import com.palantir.javaformat.Indent; import com.palantir.javaformat.LastLevelBreakability; @@ -126,7 +129,7 @@ public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State st .orElseGet(() -> { Obs.LevelNode childLevel = observer.newChildNode(this, state); State newState = - getBreakBehaviour().match(new BreakImpl(commentsHelper, maxWidth, state, childLevel)); + new BreakImpl(commentsHelper, maxWidth, state, childLevel).apply(getBreakBehaviour()); return childLevel.finishLevel(state.updateAfterLevel(newState)); }); @@ -170,7 +173,7 @@ && columnBeforeLastBreak > getColumnLimitBeforeLastBreak().getAsInt()) { return Optional.empty(); } - class BreakImpl implements BreakBehaviour.Cases { + class BreakImpl { private final CommentsHelper commentsHelper; private final int maxWidth; private final State state; @@ -184,17 +187,25 @@ public BreakImpl(CommentsHelper commentsHelper, int maxWidth, State state, Obs.L this.levelNode = levelNode; } + State apply(BreakBehaviour breakBehaviour) { + return switch (breakBehaviour) { + case BreakThisLevel() -> breakThisLevel(); + case PreferBreakingLastInnerLevel unused -> preferBreakingLastInnerLevel(); + case InlineSuffix() -> inlineSuffix(); + case BreakOnlyIfInnerLevelsThenFitOnOneLine(boolean keepIndentWhenInlined) -> + breakOnlyIfInnerLevelsThenFitOnOneLine(keepIndentWhenInlined); + }; + } + private Exploration breakNormally(State state) { return Level.this.breakNormally(state, levelNode, commentsHelper, maxWidth); } - @Override - public State breakThisLevel() { + private State breakThisLevel() { return breakNormally(state).markAccepted(); } - @Override - public State preferBreakingLastInnerLevel(boolean _keepIndentWhenInlined) { + private State preferBreakingLastInnerLevel() { int maxDepth = getMaxDepth(); // Explore both breaking and not breaking if we're in the last LAST_LEVELS_TO_EXPLORE levels. Choose the // better one based on LOC, preferring breaks if the outcome is the same. @@ -229,8 +240,7 @@ public State preferBreakingLastInnerLevel(boolean _keepIndentWhenInlined) { return breakNormally(state).markAccepted(); } - @Override - public State breakOnlyIfInnerLevelsThenFitOnOneLine(boolean keepIndentWhenInlined) { + private State breakOnlyIfInnerLevelsThenFitOnOneLine(boolean keepIndentWhenInlined) { Exploration broken = Level.this.breakNormally(state, levelNode, commentsHelper, maxWidth); Optional maybeInlined = levelNode.maybeExplore( @@ -246,8 +256,7 @@ public State breakOnlyIfInnerLevelsThenFitOnOneLine(boolean keepIndentWhenInline } } - @Override - public State inlineSuffix() { + private State inlineSuffix() { Optional lastLevelBroken = levelNode.maybeExplore( "inlineSuffix", state, @@ -265,7 +274,7 @@ private Exploration breakNormally(State state, LevelNode levelNode, CommentsHelp } /** - * Attempts to perform the {@link BreakBehaviour.Cases#breakOnlyIfInnerLevelsThenFitOnOneLine} logic, returning + * Attempts to perform the {@link BreakOnlyIfInnerLevelsThenFitOnOneLine} logic, returning * empty if it couldn't. Namely, this will only return a state if: *