From fe898e88a27f73ff8fa3a94d56a65832d954c70f Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 12 Jan 2022 15:52:56 -0800 Subject: [PATCH] Moved `PaddedCell.DirtyState` to its own top-level class with new methods. --- CHANGES.md | 1 + .../com/diffplug/spotless/DirtyState.java | 141 ++++++++++++++++++ .../com/diffplug/spotless/PaddedCell.java | 111 +------------- .../com/diffplug/gradle/spotless/IdeHook.java | 12 +- .../gradle/spotless/SpotlessTaskImpl.java | 10 +- .../spotless/maven/SpotlessApplyMojo.java | 6 +- .../spotless/maven/SpotlessCheckMojo.java | 6 +- 7 files changed, 160 insertions(+), 127 deletions(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/DirtyState.java diff --git a/CHANGES.md b/CHANGES.md index 1ba1965985..785048654b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [TBD lint release] * **BREAKING** Removed `isClean`, `applyTo`, and `applyToAndReturnResultIfDirty` from `Formatter` because users should instead use `PaddedCell.check()`. * **BREAKING** Removed `FormatterStep.Strict` because it was unnecessary and unused implementation detail. +* **BREAKING** Moved `PaddedCell.DirtyState` to its own top-level class with new methods. ## [Unreleased] ### Changed diff --git a/lib/src/main/java/com/diffplug/spotless/DirtyState.java b/lib/src/main/java/com/diffplug/spotless/DirtyState.java new file mode 100644 index 0000000000..ed6bb9ac3c --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/DirtyState.java @@ -0,0 +1,141 @@ +/* + * Copyright 2022 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.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.Arrays; + +import javax.annotation.Nullable; + +/** + * The clean/dirty state of a single file. Intended use: + * - {@link #isClean()} means that the file is is clean, and there's nothing else to say + * - {@link #didNotConverge()} means that we were unable to determine a clean state + * - once you've tested the above conditions and you know that it's a dirty file with a converged state, + * then you can call {@link #writeCanonicalTo(OutputStream)} to get the canonical form of the given file. + */ +public class DirtyState { + @Nullable + private final byte[] canonicalBytes; + + DirtyState(@Nullable byte[] canonicalBytes) { + this.canonicalBytes = canonicalBytes; + } + + public boolean isClean() { + return this == isClean; + } + + public boolean didNotConverge() { + return this == didNotConverge; + } + + private byte[] canonicalBytes() { + if (canonicalBytes == null) { + throw new IllegalStateException("First make sure that {@code !isClean()} and {@code !didNotConverge()}"); + } + return canonicalBytes; + } + + public void writeCanonicalTo(File file) throws IOException { + Files.write(file.toPath(), canonicalBytes()); + } + + public void writeCanonicalTo(OutputStream out) throws IOException { + out.write(canonicalBytes()); + } + + /** Returns the DirtyState which corresponds to {@code isClean()}. */ + public static DirtyState clean() { + return isClean; + } + + static final DirtyState didNotConverge = new DirtyState(null); + static final DirtyState isClean = new DirtyState(null); + + public static Calculation of(Formatter formatter, File file) throws IOException { + return of(formatter, file, Files.readAllBytes(file.toPath())); + } + + public static Calculation of(Formatter formatter, File file, byte[] rawBytes) { + return new Calculation(formatter, file, rawBytes); + } + + public static class Calculation { + private final Formatter formatter; + private final File file; + private final byte[] rawBytes; + private final String raw; + + private Calculation(Formatter formatter, File file, byte[] rawBytes) { + this.formatter = formatter; + this.file = file; + this.rawBytes = rawBytes; + this.raw = new String(rawBytes, formatter.getEncoding()); + // check that all characters were encodable + String encodingError = EncodingErrorMsg.msg(raw, rawBytes, formatter.getEncoding()); + if (encodingError != null) { + throw new IllegalArgumentException(encodingError); + } + } + + /** + * Calculates whether the given file is dirty according to a PaddedCell invocation of the given formatter. + * DirtyState includes the clean state of the file, as well as a warning if we were not able to apply the formatter + * due to diverging idempotence. + */ + public DirtyState calculateDirtyState() { + String rawUnix = LineEnding.toUnix(raw); + + // enforce the format + String formattedUnix = formatter.compute(rawUnix, file); + // convert the line endings if necessary + String formatted = formatter.computeLineEndings(formattedUnix, file); + + // if F(input) == input, then the formatter is well-behaving and the input is clean + byte[] formattedBytes = formatted.getBytes(formatter.getEncoding()); + if (Arrays.equals(rawBytes, formattedBytes)) { + return isClean; + } + + // F(input) != input, so we'll do a padded check + String doubleFormattedUnix = formatter.compute(formattedUnix, file); + if (doubleFormattedUnix.equals(formattedUnix)) { + // most dirty files are idempotent-dirty, so this is a quick-short circuit for that common case + return new DirtyState(formattedBytes); + } + + PaddedCell cell = PaddedCell.check(formatter, file, rawUnix); + if (!cell.isResolvable()) { + return didNotConverge; + } + + // get the canonical bytes + String canonicalUnix = cell.canonical(); + String canonical = formatter.computeLineEndings(canonicalUnix, file); + byte[] canonicalBytes = canonical.getBytes(formatter.getEncoding()); + if (!Arrays.equals(rawBytes, canonicalBytes)) { + // and write them to disk if needed + return new DirtyState(canonicalBytes); + } else { + return isClean; + } + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/PaddedCell.java b/lib/src/main/java/com/diffplug/spotless/PaddedCell.java index 59ef242f6c..4c92740780 100644 --- a/lib/src/main/java/com/diffplug/spotless/PaddedCell.java +++ b/lib/src/main/java/com/diffplug/spotless/PaddedCell.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2022 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,19 +18,14 @@ import static com.diffplug.spotless.LibPreconditions.requireElementsNonNull; import java.io.File; -import java.io.IOException; -import java.io.OutputStream; import java.nio.file.Files; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.function.Function; -import javax.annotation.Nullable; - /** * Models the result of applying a {@link Formatter} on a given {@link File} * while characterizing various failure modes (slow convergence, cycles, and divergence). @@ -176,108 +171,4 @@ public String userMessage() { } // @formatter:on } - - /** - * Calculates whether the given file is dirty according to a PaddedCell invocation of the given formatter. - * DirtyState includes the clean state of the file, as well as a warning if we were not able to apply the formatter - * due to diverging idempotence. - */ - public static DirtyState calculateDirtyState(Formatter formatter, File file) throws IOException { - Objects.requireNonNull(formatter, "formatter"); - Objects.requireNonNull(file, "file"); - - byte[] rawBytes = Files.readAllBytes(file.toPath()); - return calculateDirtyState(formatter, file, rawBytes); - } - - public static DirtyState calculateDirtyState(Formatter formatter, File file, byte[] rawBytes) throws IOException { - String raw = new String(rawBytes, formatter.getEncoding()); - // check that all characters were encodable - String encodingError = EncodingErrorMsg.msg(raw, rawBytes, formatter.getEncoding()); - if (encodingError != null) { - throw new IllegalArgumentException(encodingError); - } - String rawUnix = LineEnding.toUnix(raw); - - // enforce the format - String formattedUnix = formatter.compute(rawUnix, file); - // convert the line endings if necessary - String formatted = formatter.computeLineEndings(formattedUnix, file); - - // if F(input) == input, then the formatter is well-behaving and the input is clean - byte[] formattedBytes = formatted.getBytes(formatter.getEncoding()); - if (Arrays.equals(rawBytes, formattedBytes)) { - return isClean; - } - - // F(input) != input, so we'll do a padded check - String doubleFormattedUnix = formatter.compute(formattedUnix, file); - if (doubleFormattedUnix.equals(formattedUnix)) { - // most dirty files are idempotent-dirty, so this is a quick-short circuit for that common case - return new DirtyState(formattedBytes); - } - - PaddedCell cell = PaddedCell.check(formatter, file, rawUnix); - if (!cell.isResolvable()) { - return didNotConverge; - } - - // get the canonical bytes - String canonicalUnix = cell.canonical(); - String canonical = formatter.computeLineEndings(canonicalUnix, file); - byte[] canonicalBytes = canonical.getBytes(formatter.getEncoding()); - if (!Arrays.equals(rawBytes, canonicalBytes)) { - // and write them to disk if needed - return new DirtyState(canonicalBytes); - } else { - return isClean; - } - } - - /** - * The clean/dirty state of a single file. Intended use: - * - {@link #isClean()} means that the file is is clean, and there's nothing else to say - * - {@link #didNotConverge()} means that we were unable to determine a clean state - * - once you've tested the above conditions and you know that it's a dirty file with a converged state, - * then you can call {@link #writeCanonicalTo(OutputStream)} to get the canonical form of the given file. - */ - public static class DirtyState { - @Nullable - private final byte[] canonicalBytes; - - private DirtyState(@Nullable byte[] canonicalBytes) { - this.canonicalBytes = canonicalBytes; - } - - public boolean isClean() { - return this == isClean; - } - - public boolean didNotConverge() { - return this == didNotConverge; - } - - private byte[] canonicalBytes() { - if (canonicalBytes == null) { - throw new IllegalStateException("First make sure that {@code !isClean()} and {@code !didNotConverge()}"); - } - return canonicalBytes; - } - - public void writeCanonicalTo(File file) throws IOException { - Files.write(file.toPath(), canonicalBytes()); - } - - public void writeCanonicalTo(OutputStream out) throws IOException { - out.write(canonicalBytes()); - } - } - - /** Returns the DirtyState which corresponds to {@code isClean()}. */ - public static DirtyState isClean() { - return isClean; - } - - private static final DirtyState didNotConverge = new DirtyState(null); - private static final DirtyState isClean = new DirtyState(null); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/IdeHook.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/IdeHook.java index 8d24e2230e..5ab5a01ec3 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/IdeHook.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/IdeHook.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2022 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,11 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; import com.diffplug.common.base.Errors; import com.diffplug.common.io.ByteStreams; +import com.diffplug.spotless.DirtyState; import com.diffplug.spotless.Formatter; -import com.diffplug.spotless.PaddedCell; class IdeHook { final static String PROPERTY = "spotlessIdeHook"; @@ -49,13 +48,14 @@ static void performHook(SpotlessTaskImpl spotlessTask) { return; } } + DirtyState.Calculation init; byte[] bytes; if (spotlessTask.getProject().hasProperty(USE_STD_IN)) { - bytes = ByteStreams.toByteArray(System.in); + init = DirtyState.of(formatter, file, ByteStreams.toByteArray(System.in)); } else { - bytes = Files.readAllBytes(file.toPath()); + init = DirtyState.of(formatter, file); } - PaddedCell.DirtyState dirty = PaddedCell.calculateDirtyState(formatter, file, bytes); + DirtyState dirty = init.calculateDirtyState(); if (dirty.isClean()) { dumpIsClean(); } else if (dirty.didNotConverge()) { diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskImpl.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskImpl.java index 1b9cda204c..d787430447 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskImpl.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2022 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,8 +36,8 @@ import org.gradle.work.InputChanges; import com.diffplug.common.base.StringPrinter; +import com.diffplug.spotless.DirtyState; import com.diffplug.spotless.Formatter; -import com.diffplug.spotless.PaddedCell; import com.diffplug.spotless.extra.GitRatchet; @CacheableTask @@ -85,11 +85,11 @@ public void performAction(InputChanges inputs) throws Exception { private void processInputFile(@Nullable GitRatchet ratchet, Formatter formatter, File input) throws IOException { File output = getOutputFile(input); getLogger().debug("Applying format to " + input + " and writing to " + output); - PaddedCell.DirtyState dirtyState; + DirtyState dirtyState; if (ratchet != null && ratchet.isClean(getProjectDir().get().getAsFile(), getRootTreeSha(), input)) { - dirtyState = PaddedCell.isClean(); + dirtyState = DirtyState.clean(); } else { - dirtyState = PaddedCell.calculateDirtyState(formatter, input); + dirtyState = DirtyState.of(formatter, input).calculateDirtyState(); } if (dirtyState.isClean()) { // Remove previous output if it exists diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java index 6ea67240ee..7cc81e43fb 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2022 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; +import com.diffplug.spotless.DirtyState; import com.diffplug.spotless.Formatter; -import com.diffplug.spotless.PaddedCell; import com.diffplug.spotless.maven.incremental.UpToDateChecker; /** @@ -42,7 +42,7 @@ protected void process(Iterable files, Formatter formatter, UpToDateChecke } try { - PaddedCell.DirtyState dirtyState = PaddedCell.calculateDirtyState(formatter, file); + DirtyState dirtyState = DirtyState.of(formatter, file).calculateDirtyState(); if (!dirtyState.isClean() && !dirtyState.didNotConverge()) { dirtyState.writeCanonicalTo(file); } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessCheckMojo.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessCheckMojo.java index ad3230f583..b067bf8158 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessCheckMojo.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessCheckMojo.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2022 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,8 @@ import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; +import com.diffplug.spotless.DirtyState; import com.diffplug.spotless.Formatter; -import com.diffplug.spotless.PaddedCell; import com.diffplug.spotless.extra.integration.DiffMessageFormatter; import com.diffplug.spotless.maven.incremental.UpToDateChecker; @@ -48,7 +48,7 @@ protected void process(Iterable files, Formatter formatter, UpToDateChecke } try { - PaddedCell.DirtyState dirtyState = PaddedCell.calculateDirtyState(formatter, file); + DirtyState dirtyState = DirtyState.of(formatter, file).calculateDirtyState(); if (!dirtyState.isClean() && !dirtyState.didNotConverge()) { problemFiles.add(file); } else {