diff --git a/CHANGES.md b/CHANGES.md index cf43b4eb22..805f176185 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,14 @@ 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`). +## [TBD lint release] +* **BREAKING** Removed all deprecated methods. +* **BREAKING** Removed `isClean`, `applyTo`, and `applyToAndReturnResultIfDirty` from `Formatter` because users should instead use new class `DirtyState.of` +* **BREAKING** Removed `FormatterStep.Strict` because it was an unnecessary and unused implementation detail +* **BREAKING** Removed `rootDir` and `exceptionPolicy` parameters from `Formatter` +* **BREAKING** Renamed `PipeStepPair` to `FenceStep` and changed its approach to be lint-friendly +* **BREAKING** Modified `StepHarness` and `StepHarnessWithFile` to stop asserting on exceptions (to prepare for Lint) + ## [Unreleased] ### Changes * Bump the dev version of Gradle from `7.5.1` to `7.6` ([#1409](https://github.com/diffplug/spotless/pull/1409)) diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/integration/DiffMessageFormatter.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/integration/DiffMessageFormatter.java index 15c43c815b..8bf33d998c 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/integration/DiffMessageFormatter.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/integration/DiffMessageFormatter.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. @@ -56,15 +56,17 @@ interface CleanProvider { } private static class CleanProviderFormatter implements CleanProvider { + private final Path rootDir; private final Formatter formatter; - CleanProviderFormatter(Formatter formatter) { + CleanProviderFormatter(Path rootDir, Formatter formatter) { + this.rootDir = Objects.requireNonNull(rootDir); this.formatter = Objects.requireNonNull(formatter); } @Override public Path getRootDir() { - return formatter.getRootDir(); + return rootDir; } @Override @@ -121,8 +123,8 @@ public Builder runToFix(String runToFix) { return this; } - public Builder formatter(Formatter formatter) { - this.formatter = new CleanProviderFormatter(formatter); + public Builder formatter(Path rootDir, Formatter formatter) { + this.formatter = new CleanProviderFormatter(rootDir, formatter); return this; } diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/GitAttributesTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/GitAttributesTest.java index ed657c365a..cd09f495ae 100644 --- a/lib-extra/src/test/java/com/diffplug/spotless/extra/GitAttributesTest.java +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/GitAttributesTest.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. @@ -26,24 +26,19 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.junit.jupiter.api.Test; -import com.diffplug.common.base.Errors; import com.diffplug.common.base.StringPrinter; import com.diffplug.spotless.LineEnding; import com.diffplug.spotless.ResourceHarness; class GitAttributesTest extends ResourceHarness { private List testFiles(String prefix) { - try { - List result = new ArrayList<>(); - for (String path : TEST_PATHS) { - String prefixedPath = prefix + path; - setFile(prefixedPath).toContent(""); - result.add(newFile(prefixedPath)); - } - return result; - } catch (IOException e) { - throw Errors.asRuntime(e); + List result = new ArrayList<>(); + for (String path : TEST_PATHS) { + String prefixedPath = prefix + path; + setFile(prefixedPath).toContent(""); + result.add(newFile(prefixedPath)); } + return result; } private List testFiles() { 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..8e918fb3be --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/DirtyState.java @@ -0,0 +1,141 @@ +/* + * Copyright 2022-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.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/Formatter.java b/lib/src/main/java/com/diffplug/spotless/Formatter.java index 038e77fa64..8470b0a5c3 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 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,42 +24,29 @@ import java.io.ObjectStreamException; import java.io.Serializable; import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; -import javax.annotation.Nullable; - /** Formatter which performs the full formatting. */ public final class Formatter implements Serializable, AutoCloseable { private static final long serialVersionUID = 1L; private LineEnding.Policy lineEndingsPolicy; private Charset encoding; - private Path rootDir; private List steps; - private FormatExceptionPolicy exceptionPolicy; - private Formatter(LineEnding.Policy lineEndingsPolicy, Charset encoding, Path rootDirectory, List steps, FormatExceptionPolicy exceptionPolicy) { + private Formatter(LineEnding.Policy lineEndingsPolicy, Charset encoding, List steps) { this.lineEndingsPolicy = Objects.requireNonNull(lineEndingsPolicy, "lineEndingsPolicy"); this.encoding = Objects.requireNonNull(encoding, "encoding"); - this.rootDir = Objects.requireNonNull(rootDirectory, "rootDir"); this.steps = requireElementsNonNull(new ArrayList<>(steps)); - this.exceptionPolicy = Objects.requireNonNull(exceptionPolicy, "exceptionPolicy"); } // override serialize output private void writeObject(ObjectOutputStream out) throws IOException { out.writeObject(lineEndingsPolicy); out.writeObject(encoding.name()); - out.writeObject(rootDir.toString()); out.writeObject(steps); - out.writeObject(exceptionPolicy); } // override serialize input @@ -67,9 +54,7 @@ private void writeObject(ObjectOutputStream out) throws IOException { private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { lineEndingsPolicy = (LineEnding.Policy) in.readObject(); encoding = Charset.forName((String) in.readObject()); - rootDir = Paths.get((String) in.readObject()); steps = (List) in.readObject(); - exceptionPolicy = (FormatExceptionPolicy) in.readObject(); } // override serialize input @@ -86,18 +71,10 @@ public Charset getEncoding() { return encoding; } - public Path getRootDir() { - return rootDir; - } - public List getSteps() { return steps; } - public FormatExceptionPolicy getExceptionPolicy() { - return exceptionPolicy; - } - public static Formatter.Builder builder() { return new Formatter.Builder(); } @@ -106,9 +83,7 @@ public static class Builder { // required parameters private LineEnding.Policy lineEndingsPolicy; private Charset encoding; - private Path rootDir; private List steps; - private FormatExceptionPolicy exceptionPolicy; private Builder() {} @@ -122,84 +97,13 @@ public Builder encoding(Charset encoding) { return this; } - public Builder rootDir(Path rootDir) { - this.rootDir = rootDir; - return this; - } - public Builder steps(List steps) { this.steps = steps; return this; } - public Builder exceptionPolicy(FormatExceptionPolicy exceptionPolicy) { - this.exceptionPolicy = exceptionPolicy; - return this; - } - public Formatter build() { - return new Formatter(lineEndingsPolicy, encoding, rootDir, steps, - exceptionPolicy == null ? FormatExceptionPolicy.failOnlyOnError() : exceptionPolicy); - } - } - - /** Returns true iff the given file's formatting is up-to-date. */ - public boolean isClean(File file) throws IOException { - Objects.requireNonNull(file); - - String raw = new String(Files.readAllBytes(file.toPath()), encoding); - String unix = LineEnding.toUnix(raw); - - // check the newlines (we can find these problems without even running the steps) - int totalNewLines = (int) unix.codePoints().filter(val -> val == '\n').count(); - int windowsNewLines = raw.length() - unix.length(); - if (lineEndingsPolicy.isUnix(file)) { - if (windowsNewLines != 0) { - return false; - } - } else { - if (windowsNewLines != totalNewLines) { - return false; - } - } - - // check the other formats - String formatted = compute(unix, file); - - // return true iff the formatted string equals the unix one - return formatted.equals(unix); - } - - /** Applies formatting to the given file. */ - public void applyTo(File file) throws IOException { - applyToAndReturnResultIfDirty(file); - } - - /** - * Applies formatting to the given file. - * - * Returns null if the file was already clean, or the - * formatted result with unix newlines if it was not. - */ - public @Nullable String applyToAndReturnResultIfDirty(File file) throws IOException { - Objects.requireNonNull(file); - - byte[] rawBytes = Files.readAllBytes(file.toPath()); - String raw = new String(rawBytes, encoding); - String rawUnix = LineEnding.toUnix(raw); - - // enforce the format - String formattedUnix = compute(rawUnix, file); - // enforce the line endings - String formatted = computeLineEndings(formattedUnix, file); - - // write out the file iff it has changed - byte[] formattedBytes = formatted.getBytes(encoding); - if (!Arrays.equals(rawBytes, formattedBytes)) { - Files.write(file.toPath(), formattedBytes, StandardOpenOption.TRUNCATE_EXISTING); - return formattedUnix; - } else { - return null; + return new Formatter(lineEndingsPolicy, encoding, steps); } } @@ -237,8 +141,8 @@ public String compute(String unix, File file) { unix = LineEnding.toUnix(formatted); } } catch (Throwable e) { - String relativePath = rootDir.relativize(file.toPath()).toString(); - exceptionPolicy.handleError(e, step, relativePath); + // TODO: this is bad, but it won't matter when add support for linting + throw new RuntimeException(e); } } return unix; @@ -250,9 +154,7 @@ public int hashCode() { int result = 1; result = prime * result + encoding.hashCode(); result = prime * result + lineEndingsPolicy.hashCode(); - result = prime * result + rootDir.hashCode(); result = prime * result + steps.hashCode(); - result = prime * result + exceptionPolicy.hashCode(); return result; } @@ -270,9 +172,7 @@ public boolean equals(Object obj) { Formatter other = (Formatter) obj; return encoding.equals(other.encoding) && lineEndingsPolicy.equals(other.lineEndingsPolicy) && - rootDir.equals(other.rootDir) && - steps.equals(other.steps) && - exceptionPolicy.equals(other.exceptionPolicy); + steps.equals(other.steps); } @SuppressWarnings("rawtypes") diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterFunc.java b/lib/src/main/java/com/diffplug/spotless/FormatterFunc.java index 48a8e810ee..a820b21c7a 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterFunc.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterFunc.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. @@ -71,12 +71,6 @@ public String apply(String unix) throws Exception { }; } - /** @deprecated synonym for {@link #ofDangerous(AutoCloseable, FormatterFunc)} */ - @Deprecated - public static Closeable of(AutoCloseable closeable, FormatterFunc function) { - return ofDangerous(closeable, function); - } - @FunctionalInterface interface ResourceFunc { String apply(T resource, String unix) throws Exception; diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java index 5729f676b2..3b20590699 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-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. @@ -67,28 +67,6 @@ public default FormatterStep filterByFile(SerializableFileFilter filter) { return new FilterByFileFormatterStep(this, filter); } - /** - * Implements a FormatterStep in a strict way which guarantees correct and lazy implementation - * of up-to-date checks. This maximizes performance for cases where the FormatterStep is not - * actually needed (e.g. don't load eclipse setting file unless this step is actually running) - * while also ensuring that gradle can detect changes in a step's settings to determine that - * it needs to rerun a format. - */ - abstract class Strict extends LazyForwardingEquality implements FormatterStep { - private static final long serialVersionUID = 1L; - - /** - * Implements the formatting function strictly in terms - * of the input data and the result of {@link #calculateState()}. - */ - protected abstract String format(State state, String rawUnix, File file) throws Exception; - - @Override - public final String format(String rawUnix, File file) throws Exception { - return format(state(), rawUnix, file); - } - } - /** * @param name * The name of the formatter step diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStepImpl.java b/lib/src/main/java/com/diffplug/spotless/FormatterStepImpl.java index 7663145f51..3907d0ecff 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStepImpl.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStepImpl.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. @@ -20,8 +20,6 @@ import java.util.Objects; import java.util.Random; -import com.diffplug.spotless.FormatterStep.Strict; - import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** @@ -32,7 +30,7 @@ * from the API. */ @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") -abstract class FormatterStepImpl extends Strict { +abstract class FormatterStepImpl extends LazyForwardingEquality implements FormatterStep { private static final long serialVersionUID = 1L; /** Transient because only the state matters. */ @@ -71,15 +69,19 @@ static final class Standard extends FormatterStepImp this.stateToFormatter = Objects.requireNonNull(stateToFormatter); } + private FormatterFunc func() throws Exception { + if (formatter != null) { + return formatter; + } + formatter = stateToFormatter.apply(state()); + return formatter; + } + @Override - protected String format(State state, String rawUnix, File file) throws Exception { - Objects.requireNonNull(state, "state"); + public String format(String rawUnix, File file) throws Exception { Objects.requireNonNull(rawUnix, "rawUnix"); Objects.requireNonNull(file, "file"); - if (formatter == null) { - formatter = stateToFormatter.apply(state()); - } - return formatter.apply(rawUnix, file); + return func().apply(rawUnix, file); } void cleanupFormatterFunc() { @@ -104,15 +106,17 @@ static class NeverUpToDate extends FormatterStepImpl { this.formatterSupplier = Objects.requireNonNull(formatterSupplier, "formatterSupplier"); } - @Override - protected String format(Integer state, String rawUnix, File file) throws Exception { - if (formatter == null) { - formatter = formatterSupplier.get(); - if (formatter instanceof FormatterFunc.Closeable) { - throw new AssertionError("NeverUpToDate does not support FormatterFunc.Closeable. See https://github.com/diffplug/spotless/pull/284"); - } + private FormatterFunc func() throws Exception { + if (formatter != null) { + return formatter; } - return formatter.apply(rawUnix, file); + formatter = formatterSupplier.get(); + return formatter; + } + + @Override + public String format(String rawUnix, File file) throws Exception { + return func().apply(rawUnix, file); } } diff --git a/lib/src/main/java/com/diffplug/spotless/LineEnding.java b/lib/src/main/java/com/diffplug/spotless/LineEnding.java index 72b2532f2a..4b7a140a26 100644 --- a/lib/src/main/java/com/diffplug/spotless/LineEnding.java +++ b/lib/src/main/java/com/diffplug/spotless/LineEnding.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. @@ -102,18 +102,6 @@ public String getEndingFor(File file) { private static final Policy MAC_CLASSIC_POLICY = new ConstantLineEndingPolicy(MAC_CLASSIC.str()); private static final String _platformNative = System.getProperty("line.separator"); private static final Policy _platformNativePolicy = new ConstantLineEndingPolicy(_platformNative); - private static final boolean nativeIsWin = _platformNative.equals(WINDOWS.str()); - - /** - * @deprecated Using the system-native line endings to detect the windows operating system has turned out - * to be unreliable. Use {@link FileSignature#machineIsWin()} instead. - * - * @see FileSignature#machineIsWin() - */ - @Deprecated - public static boolean nativeIsWin() { - return nativeIsWin; - } /** Returns the standard line ending for this policy. */ public String str() { diff --git a/lib/src/main/java/com/diffplug/spotless/PaddedCell.java b/lib/src/main/java/com/diffplug/spotless/PaddedCell.java index 91914b7908..aeb881c934 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-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. @@ -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 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/lib/src/main/java/com/diffplug/spotless/generic/FenceStep.java b/lib/src/main/java/com/diffplug/spotless/generic/FenceStep.java new file mode 100644 index 0000000000..33a9adb6db --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/generic/FenceStep.java @@ -0,0 +1,217 @@ +/* + * Copyright 2020-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.generic; + +import java.io.File; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.diffplug.spotless.Formatter; +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.LineEnding; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +public class FenceStep { + /** Declares the name of the step. */ + public static FenceStep named(String name) { + return new FenceStep(name); + } + + public static String defaultToggleName() { + return "toggle"; + } + + public static String defaultToggleOff() { + return "spotless:off"; + } + + public static String defaultToggleOn() { + return "spotless:on"; + } + + String name; + Pattern regex; + + private FenceStep(String name) { + this.name = Objects.requireNonNull(name); + } + + /** Defines the opening and closing markers. */ + public FenceStep openClose(String open, String close) { + return regex(Pattern.quote(open) + "([\\s\\S]*?)" + Pattern.quote(close)); + } + + /** Defines the pipe via regex. Must have *exactly one* capturing group. */ + public FenceStep regex(String regex) { + return regex(Pattern.compile(regex)); + } + + /** Defines the pipe via regex. Must have *exactly one* capturing group. */ + public FenceStep regex(Pattern regex) { + this.regex = Objects.requireNonNull(regex); + return this; + } + + private void assertRegexSet() { + Objects.requireNonNull(regex, "must call regex() or openClose()"); + } + + /** Returns a step which will apply the given steps but preserve the content selected by the regex / openClose pair. */ + public FormatterStep preserveWithin(List steps) { + assertRegexSet(); + return FormatterStep.createLazy(name, + () -> new PreserveWithin(regex, steps), + state -> FormatterFunc.Closeable.of(state.buildFormatter(), state)); + } + + /** + * Returns a step which will apply the given steps only within the blocks selected by the regex / openClose pair. + * Linting within the substeps is not supported. + */ + public FormatterStep applyWithin(List steps) { + assertRegexSet(); + return FormatterStep.createLazy(name, + () -> new ApplyWithin(regex, steps), + state -> FormatterFunc.Closeable.of(state.buildFormatter(), state)); + } + + static class ApplyWithin extends Apply implements FormatterFunc.Closeable.ResourceFuncNeedsFile { + private static final long serialVersionUID = 17061466531957339L; + + ApplyWithin(Pattern regex, List steps) { + super(regex, steps); + } + + @Override + public String apply(Formatter formatter, String unix, File file) throws Exception { + List groups = groupsZeroed(); + Matcher matcher = regex.matcher(unix); + while (matcher.find()) { + // apply the formatter to each group + groups.add(formatter.compute(matcher.group(1), file)); + } + // and then assemble the result right away + return assembleGroups(unix); + } + } + + static class PreserveWithin extends Apply implements FormatterFunc.Closeable.ResourceFuncNeedsFile { + private static final long serialVersionUID = -8676786492305178343L; + + PreserveWithin(Pattern regex, List steps) { + super(regex, steps); + } + + private void storeGroups(String unix) { + List groups = groupsZeroed(); + Matcher matcher = regex.matcher(unix); + while (matcher.find()) { + // store whatever is within the open/close tags + groups.add(matcher.group(1)); + } + } + + @Override + public String apply(Formatter formatter, String unix, File file) throws Exception { + storeGroups(unix); + String formatted = formatter.compute(unix, file); + return assembleGroups(formatted); + } + } + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + static class Apply implements Serializable { + private static final long serialVersionUID = -2301848328356559915L; + final Pattern regex; + final List steps; + + transient ArrayList groups = new ArrayList<>(); + transient StringBuilder builderInternal; + + public Apply(Pattern regex, List steps) { + this.regex = regex; + this.steps = steps; + } + + protected ArrayList groupsZeroed() { + if (groups == null) { + groups = new ArrayList<>(); + } else { + groups.clear(); + } + return groups; + } + + private StringBuilder builderZeroed() { + if (builderInternal == null) { + builderInternal = new StringBuilder(); + } else { + builderInternal.setLength(0); + } + return builderInternal; + } + + protected Formatter buildFormatter() { + return Formatter.builder() + .encoding(StandardCharsets.UTF_8) // can be any UTF, doesn't matter + .lineEndingsPolicy(LineEnding.UNIX.createPolicy()) // just internal, won't conflict with user + .steps(steps) + .build(); + } + + protected String assembleGroups(String unix) { + if (groups.isEmpty()) { + return unix; + } + StringBuilder builder = builderZeroed(); + Matcher matcher = regex.matcher(unix); + int lastEnd = 0; + int groupIdx = 0; + while (matcher.find()) { + builder.append(unix, lastEnd, matcher.start(1)); + builder.append(groups.get(groupIdx)); + lastEnd = matcher.end(1); + ++groupIdx; + } + if (groupIdx == groups.size()) { + builder.append(unix, lastEnd, unix.length()); + return builder.toString(); + } else { + // these will be needed to generate Lints later on + // int startLine = 1 + (int) builder.toString().codePoints().filter(c -> c == '\n').count(); + // int endLine = 1 + (int) unix.codePoints().filter(c -> c == '\n').count(); + + // throw an error with either the full regex, or the nicer open/close pair + Matcher openClose = Pattern.compile("\\\\Q([\\s\\S]*?)\\\\E" + "\\Q([\\s\\S]*?)\\E" + "\\\\Q([\\s\\S]*?)\\\\E") + .matcher(regex.pattern()); + String pattern; + if (openClose.matches()) { + pattern = openClose.group(1) + " " + openClose.group(2); + } else { + pattern = regex.pattern(); + } + throw new Error("An intermediate step removed a match of " + pattern); + } + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/generic/PipeStepPair.java b/lib/src/main/java/com/diffplug/spotless/generic/PipeStepPair.java deleted file mode 100644 index 38373fec59..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/generic/PipeStepPair.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2020-2021 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.generic; - -import java.io.File; -import java.io.Serializable; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.diffplug.spotless.Formatter; -import com.diffplug.spotless.FormatterFunc; -import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.LineEnding; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -public class PipeStepPair { - /** The two steps will be named {@code In} and {@code Out}. */ - public static Builder named(String name) { - return new Builder(name); - } - - public static String defaultToggleName() { - return "toggle"; - } - - public static String defaultToggleOff() { - return "spotless:off"; - } - - public static String defaultToggleOn() { - return "spotless:on"; - } - - public static class Builder { - String name; - Pattern regex; - - private Builder(String name) { - this.name = Objects.requireNonNull(name); - } - - /** Defines the opening and closing markers. */ - public Builder openClose(String open, String close) { - return regex(Pattern.quote(open) + "([\\s\\S]*?)" + Pattern.quote(close)); - } - - /** Defines the pipe via regex. Must have *exactly one* capturing group. */ - public Builder regex(String regex) { - return regex(Pattern.compile(regex)); - } - - /** Defines the pipe via regex. Must have *exactly one* capturing group. */ - public Builder regex(Pattern regex) { - this.regex = Objects.requireNonNull(regex); - return this; - } - - /** Returns a pair of steps which captures in the first part, then returns in the second. */ - public PipeStepPair buildPair() { - return new PipeStepPair(name, regex); - } - - /** Returns a single step which will apply the given steps only within the blocks selected by the regex / openClose pair. */ - public FormatterStep buildStepWhichAppliesSubSteps(Path rootPath, Collection steps) { - return FormatterStep.createLazy(name, - () -> new StateApplyToBlock(regex, steps), - state -> FormatterFunc.Closeable.of(state.buildFormatter(rootPath), state::format)); - } - } - - final FormatterStep in, out; - - private PipeStepPair(String name, Pattern pattern) { - StateIn stateIn = new StateIn(pattern); - StateOut stateOut = new StateOut(stateIn); - in = FormatterStep.create(name + "In", stateIn, state -> state::format); - out = FormatterStep.create(name + "Out", stateOut, state -> state::format); - } - - public FormatterStep in() { - return in; - } - - public FormatterStep out() { - return out; - } - - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - static class StateApplyToBlock extends StateIn implements Serializable { - private static final long serialVersionUID = -844178006407733370L; - - final List steps; - final transient StringBuilder builder = new StringBuilder(); - - StateApplyToBlock(Pattern regex, Collection steps) { - super(regex); - this.steps = new ArrayList<>(steps); - } - - Formatter buildFormatter(Path rootDir) { - return Formatter.builder() - .encoding(StandardCharsets.UTF_8) // can be any UTF, doesn't matter - .lineEndingsPolicy(LineEnding.UNIX.createPolicy()) // just internal, won't conflict with user - .steps(steps) - .rootDir(rootDir) - .build(); - } - - private String format(Formatter formatter, String unix, File file) throws Exception { - groups.clear(); - Matcher matcher = regex.matcher(unix); - while (matcher.find()) { - // apply the formatter to each group - groups.add(formatter.compute(matcher.group(1), file)); - } - // and then assemble the result right away - return stateOutCompute(this, builder, unix); - } - } - - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - static class StateIn implements Serializable { - private static final long serialVersionUID = -844178006407733370L; - - final Pattern regex; - - public StateIn(Pattern regex) { - this.regex = Objects.requireNonNull(regex); - } - - final transient ArrayList groups = new ArrayList<>(); - - private String format(String unix) throws Exception { - groups.clear(); - Matcher matcher = regex.matcher(unix); - while (matcher.find()) { - groups.add(matcher.group(1)); - } - return unix; - } - } - - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - static class StateOut implements Serializable { - private static final long serialVersionUID = -1195263184715054229L; - - final StateIn in; - - StateOut(StateIn in) { - this.in = Objects.requireNonNull(in); - } - - final transient StringBuilder builder = new StringBuilder(); - - private String format(String unix) { - return stateOutCompute(in, builder, unix); - } - } - - private static String stateOutCompute(StateIn in, StringBuilder builder, String unix) { - if (in.groups.isEmpty()) { - return unix; - } - builder.setLength(0); - Matcher matcher = in.regex.matcher(unix); - int lastEnd = 0; - int groupIdx = 0; - while (matcher.find()) { - builder.append(unix, lastEnd, matcher.start(1)); - builder.append(in.groups.get(groupIdx)); - lastEnd = matcher.end(1); - ++groupIdx; - } - if (groupIdx == in.groups.size()) { - builder.append(unix, lastEnd, unix.length()); - return builder.toString(); - } else { - // throw an error with either the full regex, or the nicer open/close pair - Matcher openClose = Pattern.compile("\\\\Q([\\s\\S]*?)\\\\E" + "\\Q([\\s\\S]*?)\\E" + "\\\\Q([\\s\\S]*?)\\\\E") - .matcher(in.regex.pattern()); - String pattern; - if (openClose.matches()) { - pattern = openClose.group(1) + " " + openClose.group(2); - } else { - pattern = in.regex.pattern(); - } - throw new Error("An intermediate step removed a match of " + pattern); - } - } -} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index de0313eab4..a9ccea3f03 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.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. @@ -23,6 +23,7 @@ import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -51,11 +52,11 @@ import com.diffplug.spotless.extra.EclipseBasedStepBuilder; import com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep; import com.diffplug.spotless.generic.EndWithNewlineStep; +import com.diffplug.spotless.generic.FenceStep; import com.diffplug.spotless.generic.IndentStep; import com.diffplug.spotless.generic.LicenseHeaderStep; import com.diffplug.spotless.generic.LicenseHeaderStep.YearMode; import com.diffplug.spotless.generic.NativeCmdStep; -import com.diffplug.spotless.generic.PipeStepPair; import com.diffplug.spotless.generic.ReplaceRegexStep; import com.diffplug.spotless.generic.ReplaceStep; import com.diffplug.spotless.generic.TrimTrailingWhitespaceStep; @@ -697,7 +698,7 @@ public void withinBlocks(String name, String open, String close, Action */ public void withinBlocks(String name, String open, String close, Class clazz, Action configure) { - withinBlocksHelper(PipeStepPair.named(name).openClose(open, close), clazz, configure); + withinBlocksHelper(FenceStep.named(name).openClose(open, close), clazz, configure); } /** Same as {@link #withinBlocks(String, String, String, Action)}, except instead of an open/close pair, you specify a regex with exactly one capturing group. */ @@ -707,17 +708,16 @@ public void withinBlocksRegex(String name, String regex, Action /** Same as {@link #withinBlocksRegex(String, String, Action)}, except you can specify any language-specific subclass of {@link FormatExtension} to get language-specific steps. */ public void withinBlocksRegex(String name, String regex, Class clazz, Action configure) { - withinBlocksHelper(PipeStepPair.named(name).regex(regex), clazz, configure); + withinBlocksHelper(FenceStep.named(name).regex(regex), clazz, configure); } - private void withinBlocksHelper(PipeStepPair.Builder builder, Class clazz, Action configure) { + private void withinBlocksHelper(FenceStep builder, Class clazz, Action configure) { // create the sub-extension T formatExtension = spotless.instantiateFormatExtension(clazz); // configure it configure.execute(formatExtension); // create a step which applies all of those steps as sub-steps - FormatterStep step = builder.buildStepWhichAppliesSubSteps(spotless.project.getRootDir().toPath(), formatExtension.steps); - addStep(step); + addStep(builder.applyWithin(formatExtension.steps)); } /** @@ -725,25 +725,25 @@ private void withinBlocksHelper(PipeStepPair.Builder * inside that captured group. */ public void toggleOffOnRegex(String regex) { - this.togglePair = PipeStepPair.named(PipeStepPair.defaultToggleName()).regex(regex).buildPair(); + this.toggleFence = FenceStep.named(FenceStep.defaultToggleName()).regex(regex); } /** Disables formatting between the given tags. */ public void toggleOffOn(String off, String on) { - this.togglePair = PipeStepPair.named(PipeStepPair.defaultToggleName()).openClose(off, on).buildPair(); + this.toggleFence = FenceStep.named(FenceStep.defaultToggleName()).openClose(off, on); } /** Disables formatting between {@code spotless:off} and {@code spotless:on}. */ public void toggleOffOn() { - toggleOffOn(PipeStepPair.defaultToggleOff(), PipeStepPair.defaultToggleOn()); + toggleOffOn(FenceStep.defaultToggleOff(), FenceStep.defaultToggleOn()); } /** Undoes all previous calls to {@link #toggleOffOn()} and {@link #toggleOffOn(String, String)}. */ public void toggleOffOnDisable() { - this.togglePair = null; + this.toggleFence = null; } - private @Nullable PipeStepPair togglePair; + private @Nullable FenceStep toggleFence; /** Sets up a format task according to the values in this extension. */ protected void setupTask(SpotlessTask task) { @@ -752,11 +752,8 @@ protected void setupTask(SpotlessTask task) { FileCollection totalTarget = targetExclude == null ? target : target.minus(targetExclude); task.setTarget(totalTarget); List steps; - if (togglePair != null) { - steps = new ArrayList<>(this.steps.size() + 2); - steps.add(togglePair.in()); - steps.addAll(this.steps); - steps.add(togglePair.out()); + if (toggleFence != null) { + steps = Collections.singletonList(toggleFence.preserveWithin(this.steps)); } else { steps = this.steps; } 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..b701657cee 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-2023 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/SpotlessTask.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java index 6f2279b2e0..e233cfdb36 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -186,9 +186,7 @@ Formatter buildFormatter() { return Formatter.builder() .lineEndingsPolicy(lineEndingsPolicy.get()) .encoding(Charset.forName(encoding)) - .rootDir(getProjectDir().get().getAsFile().toPath()) .steps(steps.get()) - .exceptionPolicy(exceptionPolicy) .build(); } } 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 b37e9f283a..9137643c50 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-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. @@ -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 @@ -95,11 +95,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-gradle/src/test/java/com/diffplug/gradle/spotless/ErrorShouldRethrowTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ErrorShouldRethrowTest.java index d1ced01609..692206529d 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ErrorShouldRethrowTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ErrorShouldRethrowTest.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. @@ -23,6 +23,7 @@ import org.assertj.core.api.Assertions; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import com.diffplug.common.base.CharMatcher; @@ -30,6 +31,7 @@ import com.diffplug.spotless.LineEnding; /** Tests the desired behavior from https://github.com/diffplug/spotless/issues/46. */ +@Disabled("Because Formatter doesn't respect FormatExceptionPolicy anymore") class ErrorShouldRethrowTest extends GradleIntegrationHarness { private void writeBuild(String... toInsert) throws IOException { List lines = new ArrayList<>(); diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java index e4fdc7dcd0..66e9e214cd 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.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. @@ -114,7 +114,7 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo { private List repositories; @Parameter(defaultValue = "${project.basedir}", required = true, readonly = true) - private File baseDir; + protected File baseDir; @Parameter(defaultValue = "${project.build.directory}", required = true, readonly = true) private File buildDir; diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java index d5fbe60370..b86973078a 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.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. @@ -21,6 +21,7 @@ import java.io.File; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -31,11 +32,9 @@ import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.common.collect.Sets; -import com.diffplug.spotless.FormatExceptionPolicyStrict; import com.diffplug.spotless.Formatter; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.LineEnding; -import com.diffplug.spotless.generic.PipeStepPair; import com.diffplug.spotless.maven.generic.EclipseWtp; import com.diffplug.spotless.maven.generic.EndWithNewline; import com.diffplug.spotless.maven.generic.Indent; @@ -96,17 +95,13 @@ public final Formatter newFormatter(Supplier> filesToFormat, Form .map(factory -> factory.newFormatterStep(stepConfig)) .collect(Collectors.toCollection(() -> new ArrayList())); if (toggle != null) { - PipeStepPair pair = toggle.createPair(); - formatterSteps.add(0, pair.in()); - formatterSteps.add(pair.out()); + List formatterStepsBeforeToggle = formatterSteps; + formatterSteps = Collections.singletonList(toggle.createFence().preserveWithin(formatterStepsBeforeToggle)); } - return Formatter.builder() .encoding(formatterEncoding) .lineEndingsPolicy(formatterLineEndingPolicy) - .exceptionPolicy(new FormatExceptionPolicyStrict()) .steps(formatterSteps) - .rootDir(config.getFileLocator().getBaseDir().toPath()) .build(); } 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 7a15d01b17..4665889514 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-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. @@ -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); buildContext.refresh(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..3ab0084fd7 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-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,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 { @@ -62,7 +62,7 @@ protected void process(Iterable files, Formatter formatter, UpToDateChecke if (!problemFiles.isEmpty()) { throw new MojoExecutionException(DiffMessageFormatter.builder() .runToFix("Run 'mvn spotless:apply' to fix these violations.") - .formatter(formatter) + .formatter(baseDir.toPath(), formatter) .problemFiles(problemFiles) .getMessage()); } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/ToggleOffOn.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/ToggleOffOn.java index fe1676aec9..81ad79083f 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/ToggleOffOn.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/ToggleOffOn.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,23 +17,23 @@ import org.apache.maven.plugins.annotations.Parameter; -import com.diffplug.spotless.generic.PipeStepPair; +import com.diffplug.spotless.generic.FenceStep; public class ToggleOffOn { @Parameter - public String off = PipeStepPair.defaultToggleOff(); + public String off = FenceStep.defaultToggleOff(); @Parameter - public String on = PipeStepPair.defaultToggleOn(); + public String on = FenceStep.defaultToggleOn(); @Parameter public String regex; - public PipeStepPair createPair() { + public FenceStep createFence() { if (regex != null) { - return PipeStepPair.named(PipeStepPair.defaultToggleName()).regex(regex).buildPair(); + return FenceStep.named(FenceStep.defaultToggleName()).regex(regex); } else { - return PipeStepPair.named(PipeStepPair.defaultToggleName()).openClose(off, on).buildPair(); + return FenceStep.named(FenceStep.defaultToggleName()).openClose(off, on); } } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/NoopCheckerTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/NoopCheckerTest.java index 404b5e2191..7461761ade 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/NoopCheckerTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/NoopCheckerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import org.apache.maven.model.Build; import org.apache.maven.model.Plugin; @@ -37,7 +36,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import com.diffplug.spotless.FormatExceptionPolicyStrict; import com.diffplug.spotless.Formatter; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.LineEnding; @@ -116,11 +114,9 @@ private MavenProject buildMavenProject() throws IOException { private static Formatter dummyFormatter() { return Formatter.builder() - .rootDir(Paths.get("")) .lineEndingsPolicy(LineEnding.UNIX.createPolicy()) .encoding(UTF_8) .steps(singletonList(mock(FormatterStep.class, withSettings().serializable()))) - .exceptionPolicy(new FormatExceptionPolicyStrict()) .build(); } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/PluginFingerprintTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/PluginFingerprintTest.java index 90e59d657e..f6e0af17b8 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/PluginFingerprintTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/PluginFingerprintTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.io.ByteArrayInputStream; -import java.nio.file.Paths; import java.util.Arrays; import java.util.List; @@ -32,7 +31,6 @@ import org.codehaus.plexus.util.xml.XmlStreamReader; import org.junit.jupiter.api.Test; -import com.diffplug.spotless.FormatExceptionPolicyStrict; import com.diffplug.spotless.Formatter; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.LineEnding; @@ -246,11 +244,9 @@ private static Formatter formatter(FormatterStep... steps) { private static Formatter formatter(LineEnding lineEnding, FormatterStep... steps) { return Formatter.builder() - .rootDir(Paths.get("")) .lineEndingsPolicy(lineEnding.createPolicy()) .encoding(UTF_8) .steps(Arrays.asList(steps)) - .exceptionPolicy(new FormatExceptionPolicyStrict()) .build(); } } diff --git a/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java b/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java index b4a8bbae24..8182596e39 100644 --- a/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java +++ b/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.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. @@ -54,7 +54,7 @@ protected File rootFolder() { } /** Returns a new child of the root folder. */ - protected File newFile(String subpath) throws IOException { + protected File newFile(String subpath) { return new File(rootFolder(), subpath); } @@ -85,12 +85,12 @@ protected void replace(String path, String toReplace, String replaceWith) throws } /** Returns the contents of the given file from the src/test/resources directory. */ - protected static String getTestResource(String filename) throws IOException { + protected static String getTestResource(String filename) { URL url = ResourceHarness.class.getResource("/" + filename); if (url == null) { throw new IllegalArgumentException("No such resource " + filename); } - return Resources.toString(url, StandardCharsets.UTF_8); + return ThrowingEx.get(() -> LineEnding.toUnix(Resources.toString(url, StandardCharsets.UTF_8))); } /** Returns Files (in a temporary folder) which has the contents of the given file from the src/test/resources directory. */ @@ -176,7 +176,7 @@ public void matches(Consumer> conditions) } } - protected WriteAsserter setFile(String path) throws IOException { + protected WriteAsserter setFile(String path) { return new WriteAsserter(newFile(path)); } @@ -188,21 +188,25 @@ private WriteAsserter(File file) { this.file = file; } - public File toLines(String... lines) throws IOException { + public File toLines(String... lines) { return toContent(String.join("\n", Arrays.asList(lines))); } - public File toContent(String content) throws IOException { + public File toContent(String content) { return toContent(content, StandardCharsets.UTF_8); } - public File toContent(String content, Charset charset) throws IOException { - Files.write(file.toPath(), content.getBytes(charset)); + public File toContent(String content, Charset charset) { + ThrowingEx.run(() -> { + Files.write(file.toPath(), content.getBytes(charset)); + }); return file; } - public File toResource(String path) throws IOException { - Files.write(file.toPath(), getTestResource(path).getBytes(StandardCharsets.UTF_8)); + public File toResource(String path) { + ThrowingEx.run(() -> { + Files.write(file.toPath(), getTestResource(path).getBytes(StandardCharsets.UTF_8)); + }); return file; } diff --git a/testlib/src/main/java/com/diffplug/spotless/StepHarness.java b/testlib/src/main/java/com/diffplug/spotless/StepHarness.java index 8755f852d6..1dd3bea937 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-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. @@ -19,34 +19,23 @@ import java.io.File; import java.nio.charset.StandardCharsets; -import java.nio.file.Paths; import java.util.Arrays; import java.util.Objects; -import java.util.function.Consumer; -import org.assertj.core.api.AbstractThrowableAssert; +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 FormatterFunc formatter; + private final Formatter formatter; - private StepHarness(FormatterFunc formatter) { + private StepHarness(Formatter formatter) { this.formatter = Objects.requireNonNull(formatter); } /** Creates a harness for testing steps which don't depend on the file. */ public static StepHarness forStep(FormatterStep step) { - // We don't care if an individual FormatterStep is misbehaving on line-endings, because - // Formatter fixes that. No reason to care in tests either. It's likely to pop up when - // running tests on Windows from time-to-time - return new StepHarness(FormatterFunc.Closeable.ofDangerous( - () -> { - if (step instanceof FormatterStepImpl.Standard) { - ((FormatterStepImpl.Standard) step).cleanupFormatterFunc(); - } - }, - input -> LineEnding.toUnix(step.format(input, new File(""))))); + return forSteps(step); } /** Creates a harness for testing steps which don't depend on the file. */ @@ -55,61 +44,67 @@ public static StepHarness forSteps(FormatterStep... steps) { .steps(Arrays.asList(steps)) .lineEndingsPolicy(LineEnding.UNIX.createPolicy()) .encoding(StandardCharsets.UTF_8) - .rootDir(Paths.get("")) .build()); } /** Creates a harness for testing a formatter whose steps don't depend on the file. */ public static StepHarness forFormatter(Formatter formatter) { - return new StepHarness(FormatterFunc.Closeable.ofDangerous( - formatter::close, - input -> formatter.compute(input, new File("")))); + return new StepHarness(formatter); } /** Asserts that the given element is transformed as expected, and that the result is idempotent. */ - public StepHarness test(String before, String after) throws Exception { - String actual = formatter.apply(before); + public StepHarness test(String before, String after) { + 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) throws Exception { - String actual = formatter.apply(idempotentElement); + public StepHarness testUnaffected(String idempotentElement) { + String actual = formatter.compute(LineEnding.toUnix(idempotentElement), new File("")); assertEquals(idempotentElement, actual, "Step is not idempotent"); return this; } /** Asserts that the given elements in the resources directory are transformed as expected. */ - public StepHarness testResource(String resourceBefore, String resourceAfter) throws Exception { + public StepHarness testResource(String resourceBefore, String resourceAfter) { String before = ResourceHarness.getTestResource(resourceBefore); String after = ResourceHarness.getTestResource(resourceAfter); return test(before, after); } /** Asserts that the given elements in the resources directory are transformed as expected. */ - public StepHarness testResourceUnaffected(String resourceIdempotent) throws Exception { + public StepHarness testResourceUnaffected(String resourceIdempotent) { String idempotentElement = ResourceHarness.getTestResource(resourceIdempotent); return testUnaffected(idempotentElement); } - /** Asserts that the given elements in the resources directory are transformed as expected. */ - public StepHarness testResourceException(String resourceBefore, Consumer> exceptionAssertion) throws Exception { - return testException(ResourceHarness.getTestResource(resourceBefore), exceptionAssertion); + public AbstractStringAssert testResourceExceptionMsg(String resourceBefore) { + return testExceptionMsg(ResourceHarness.getTestResource(resourceBefore)); } - /** Asserts that the given elements in the resources directory are transformed as expected. */ - public StepHarness testException(String before, Consumer> exceptionAssertion) throws Exception { - Throwable t = assertThrows(Throwable.class, () -> formatter.apply(before)); - AbstractThrowableAssert abstractAssert = Assertions.assertThat(t); - exceptionAssertion.accept(abstractAssert); - return this; + public AbstractStringAssert testExceptionMsg(String before) { + try { + formatter.compute(LineEnding.toUnix(before), FormatterStepImpl.SENTINEL); + throw new SecurityException("Expected exception"); + } catch (Throwable e) { + if (e instanceof SecurityException) { + throw new AssertionError(e.getMessage()); + } else { + Throwable rootCause = e; + while (rootCause.getCause() != null) { + if (rootCause instanceof IllegalStateException) { + break; + } + rootCause = rootCause.getCause(); + } + return Assertions.assertThat(rootCause.getMessage()); + } + } } @Override public void close() { - if (formatter instanceof FormatterFunc.Closeable) { - ((FormatterFunc.Closeable) formatter).close(); - } + formatter.close(); } } diff --git a/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java b/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java index 89be961d04..d22e8e3db9 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-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. @@ -18,78 +18,98 @@ import static org.junit.jupiter.api.Assertions.*; import java.io.File; +import java.nio.charset.StandardCharsets; +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 depends on the File path. */ public class StepHarnessWithFile implements AutoCloseable { - private final FormatterFunc formatter; + private final Formatter formatter; + private ResourceHarness harness; - private StepHarnessWithFile(FormatterFunc formatter) { + private StepHarnessWithFile(ResourceHarness harness, Formatter formatter) { + this.harness = Objects.requireNonNull(harness); this.formatter = Objects.requireNonNull(formatter); } /** Creates a harness for testing steps which do depend on the file. */ - public static StepHarnessWithFile forStep(FormatterStep step) { - // We don't care if an individual FormatterStep is misbehaving on line-endings, because - // Formatter fixes that. No reason to care in tests either. It's likely to pop up when - // running tests on Windows from time-to-time - return new StepHarnessWithFile(FormatterFunc.Closeable.ofDangerous( - () -> { - if (step instanceof FormatterStepImpl.Standard) { - ((FormatterStepImpl.Standard) step).cleanupFormatterFunc(); - } - }, - new FormatterFunc() { - @Override - public String apply(String unix) throws Exception { - return apply(unix, new File("")); - } - - @Override - public String apply(String unix, File file) throws Exception { - return LineEnding.toUnix(step.format(unix, file)); - } - })); + public static StepHarnessWithFile forStep(ResourceHarness harness, FormatterStep step) { + return new StepHarnessWithFile(harness, Formatter.builder() + .encoding(StandardCharsets.UTF_8) + .lineEndingsPolicy(LineEnding.UNIX.createPolicy()) + .steps(Arrays.asList(step)) + .build()); } /** Creates a harness for testing a formatter whose steps do depend on the file. */ - public static StepHarnessWithFile forFormatter(Formatter formatter) { - return new StepHarnessWithFile(FormatterFunc.Closeable.ofDangerous( - formatter::close, - input -> formatter.compute(input, new File("")))); + public static StepHarnessWithFile forFormatter(ResourceHarness harness, Formatter formatter) { + return new StepHarnessWithFile(harness, formatter); } /** Asserts that the given element is transformed as expected, and that the result is idempotent. */ - public StepHarnessWithFile test(File file, String before, String after) throws Exception { - String actual = formatter.apply(before, file); + public StepHarnessWithFile test(File file, String before, String after) { + 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) throws Exception { - String actual = formatter.apply(idempotentElement, file); + public StepHarnessWithFile testUnaffected(File file, String idempotentElement) { + String actual = formatter.compute(LineEnding.toUnix(idempotentElement), file); assertEquals(idempotentElement, actual, "Step is not idempotent"); return this; } /** Asserts that the given elements in the resources directory are transformed as expected. */ - public StepHarnessWithFile testResource(File file, String resourceBefore, String resourceAfter) throws Exception { - String before = ResourceHarness.getTestResource(resourceBefore); - String after = ResourceHarness.getTestResource(resourceAfter); - return test(file, before, after); + public StepHarnessWithFile testResource(String resourceBefore, String resourceAfter) { + return testResource(resourceBefore, resourceBefore, resourceAfter); + } + + 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)); } /** Asserts that the given elements in the resources directory are transformed as expected. */ - public StepHarnessWithFile testResourceUnaffected(File file, String resourceIdempotent) throws Exception { - String idempotentElement = ResourceHarness.getTestResource(resourceIdempotent); - return testUnaffected(file, idempotentElement); + public StepHarnessWithFile testResourceUnaffected(String resourceIdempotent) { + String contentBefore = ResourceHarness.getTestResource(resourceIdempotent); + File file = harness.setFile(resourceIdempotent).toContent(contentBefore); + return testUnaffected(file, contentBefore); + } + + public AbstractStringAssert testResourceExceptionMsg(String resourceBefore) { + return testResourceExceptionMsg(resourceBefore, resourceBefore); + } + + public AbstractStringAssert testResourceExceptionMsg(String filename, String resourceBefore) { + String contentBefore = ResourceHarness.getTestResource(resourceBefore); + File file = harness.setFile(filename).toContent(contentBefore); + return testExceptionMsg(file, contentBefore); + } + + public AbstractStringAssert testExceptionMsg(File file, String before) { + try { + formatter.compute(LineEnding.toUnix(before), file); + throw new SecurityException("Expected exception"); + } catch (Throwable e) { + if (e instanceof SecurityException) { + throw new AssertionError(e.getMessage()); + } else { + Throwable rootCause = e; + while (rootCause.getCause() != null) { + rootCause = rootCause.getCause(); + } + return Assertions.assertThat(rootCause.getMessage()); + } + } } @Override public void close() { - if (formatter instanceof FormatterFunc.Closeable) { - ((FormatterFunc.Closeable) formatter).close(); - } + formatter.close(); } } diff --git a/testlib/src/test/java/com/diffplug/spotless/FormatterTest.java b/testlib/src/test/java/com/diffplug/spotless/FormatterTest.java index 06b8e64d31..af5c83d490 100644 --- a/testlib/src/test/java/com/diffplug/spotless/FormatterTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/FormatterTest.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. @@ -17,15 +17,12 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import com.diffplug.common.base.StandardSystemProperty; import com.diffplug.spotless.generic.EndWithNewlineStep; class FormatterTest { @@ -42,9 +39,7 @@ void equality() { new SerializableEqualityTester() { private LineEnding.Policy lineEndingsPolicy = LineEnding.UNIX.createPolicy(); private Charset encoding = StandardCharsets.UTF_8; - private Path rootDir = Paths.get(StandardSystemProperty.USER_DIR.value()); private List steps = new ArrayList<>(); - private FormatExceptionPolicy exceptionPolicy = FormatExceptionPolicy.failOnlyOnError(); @Override protected void setupTest(API api) throws Exception { @@ -56,25 +51,8 @@ protected void setupTest(API api) throws Exception { encoding = StandardCharsets.UTF_16; api.areDifferentThan(); - rootDir = rootDir.getParent(); - api.areDifferentThan(); - steps.add(EndWithNewlineStep.create()); api.areDifferentThan(); - - { - FormatExceptionPolicyStrict standard = new FormatExceptionPolicyStrict(); - standard.excludePath("path"); - exceptionPolicy = standard; - api.areDifferentThan(); - } - - { - FormatExceptionPolicyStrict standard = new FormatExceptionPolicyStrict(); - standard.excludeStep("step"); - exceptionPolicy = standard; - api.areDifferentThan(); - } } @Override @@ -82,9 +60,7 @@ protected Formatter create() { return Formatter.builder() .lineEndingsPolicy(lineEndingsPolicy) .encoding(encoding) - .rootDir(rootDir) .steps(steps) - .exceptionPolicy(exceptionPolicy) .build(); } }.testEquals(); diff --git a/testlib/src/test/java/com/diffplug/spotless/PaddedCellTest.java b/testlib/src/test/java/com/diffplug/spotless/PaddedCellTest.java index dae41678a4..92b63f5841 100644 --- a/testlib/src/test/java/com/diffplug/spotless/PaddedCellTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/PaddedCellTest.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. @@ -51,7 +51,6 @@ private void testCase(FormatterFunc step, String input, PaddedCell.Type expected try (Formatter formatter = Formatter.builder() .lineEndingsPolicy(LineEnding.UNIX.createPolicy()) .encoding(StandardCharsets.UTF_8) - .rootDir(rootFolder.toPath()) .steps(formatterSteps).build()) { File file = new File(rootFolder, "input"); diff --git a/testlib/src/test/java/com/diffplug/spotless/cpp/ClangFormatStepTest.java b/testlib/src/test/java/com/diffplug/spotless/cpp/ClangFormatStepTest.java index cf0f551485..2a23b9b73e 100644 --- a/testlib/src/test/java/com/diffplug/spotless/cpp/ClangFormatStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/cpp/ClangFormatStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-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,26 +15,26 @@ */ package com.diffplug.spotless.cpp; -import java.io.File; import java.util.Arrays; import org.junit.jupiter.api.Test; +import com.diffplug.spotless.ResourceHarness; import com.diffplug.spotless.StepHarnessWithFile; import com.diffplug.spotless.tag.ClangTest; @ClangTest -class ClangFormatStepTest { +class ClangFormatStepTest extends ResourceHarness { @Test void test() throws Exception { - try (StepHarnessWithFile harness = StepHarnessWithFile.forStep(ClangFormatStep.withVersion(ClangFormatStep.defaultVersion()).create())) { + try (StepHarnessWithFile harness = StepHarnessWithFile.forStep(this, ClangFormatStep.withVersion(ClangFormatStep.defaultVersion()).create())) { // can't be named java or it gets compiled into .class file - harness.testResource(new File("example.java"), "clang/example.java.dirty", "clang/example.java.clean"); + harness.testResource("example.java", "clang/example.java.dirty", "clang/example.java.clean"); // test every other language clang supports for (String ext : Arrays.asList("c", "cs", "js", "m", "proto")) { String filename = "example." + ext; String root = "clang/" + filename; - harness.testResource(new File(filename), root, root + ".clean"); + harness.testResource(filename, root, root + ".clean"); } } } diff --git a/testlib/src/test/java/com/diffplug/spotless/generic/PipeStepPairTest.java b/testlib/src/test/java/com/diffplug/spotless/generic/FenceStepTest.java similarity index 55% rename from testlib/src/test/java/com/diffplug/spotless/generic/PipeStepPairTest.java rename to testlib/src/test/java/com/diffplug/spotless/generic/FenceStepTest.java index 6bb144e6b4..37e3131cd0 100644 --- a/testlib/src/test/java/com/diffplug/spotless/generic/PipeStepPairTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/generic/FenceStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-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,7 +15,6 @@ */ package com.diffplug.spotless.generic; -import java.nio.file.Paths; import java.util.Arrays; import java.util.Locale; @@ -23,14 +22,16 @@ import com.diffplug.common.base.StringPrinter; import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.ResourceHarness; import com.diffplug.spotless.StepHarness; +import com.diffplug.spotless.StepHarnessWithFile; -class PipeStepPairTest { +class FenceStepTest extends ResourceHarness { @Test - void single() throws Exception { - PipeStepPair pair = PipeStepPair.named("underTest").openClose("spotless:off", "spotless:on").buildPair(); - FormatterStep lowercase = FormatterStep.createNeverUpToDate("lowercase", str -> str.toLowerCase(Locale.ROOT)); - StepHarness harness = StepHarness.forSteps(pair.in(), lowercase, pair.out()); + void single() { + FormatterStep fence = FenceStep.named("underTest").openClose("spotless:off", "spotless:on") + .preserveWithin(Arrays.asList(FormatterStep.createNeverUpToDate("lowercase", str -> str.toLowerCase(Locale.ROOT)))); + StepHarness harness = StepHarness.forSteps(fence); harness.test( StringPrinter.buildStringFromLines( "A B C", @@ -47,10 +48,10 @@ void single() throws Exception { } @Test - void multiple() throws Exception { - PipeStepPair pair = PipeStepPair.named("underTest").openClose("spotless:off", "spotless:on").buildPair(); - FormatterStep lowercase = FormatterStep.createNeverUpToDate("lowercase", str -> str.toLowerCase(Locale.ROOT)); - StepHarness harness = StepHarness.forSteps(pair.in(), lowercase, pair.out()); + void multiple() { + FormatterStep fence = FenceStep.named("underTest").openClose("spotless:off", "spotless:on") + .preserveWithin(Arrays.asList(FormatterStep.createNeverUpToDate("lowercase", str -> str.toLowerCase(Locale.ROOT)))); + StepHarness harness = StepHarness.forSteps(fence); harness.test( StringPrinter.buildStringFromLines( "A B C", @@ -81,27 +82,24 @@ void multiple() throws Exception { } @Test - void broken() throws Exception { - PipeStepPair pair = PipeStepPair.named("underTest").openClose("spotless:off", "spotless:on").buildPair(); - FormatterStep uppercase = FormatterStep.createNeverUpToDate("uppercase", str -> str.toUpperCase(Locale.ROOT)); - StepHarness harness = StepHarness.forSteps(pair.in(), uppercase, pair.out()); + void broken() { + FormatterStep fence = FenceStep.named("underTest").openClose("spotless:off", "spotless:on") + .preserveWithin(Arrays.asList(FormatterStep.createNeverUpToDate("uppercase", str -> str.toUpperCase(Locale.ROOT)))); + StepHarnessWithFile harness = StepHarnessWithFile.forStep(this, fence); // this fails because uppercase turns spotless:off into SPOTLESS:OFF, etc - harness.testException(StringPrinter.buildStringFromLines( - "A B C", + harness.testExceptionMsg(newFile("test"), StringPrinter.buildStringFromLines("A B C", "spotless:off", "D E F", "spotless:on", - "G H I"), exception -> { - exception.hasMessage("An intermediate step removed a match of spotless:off spotless:on"); - }); + "G H I")).isEqualTo("An intermediate step removed a match of spotless:off spotless:on"); } @Test - void andApply() throws Exception { - FormatterStep lowercase = FormatterStep.createNeverUpToDate("lowercase", str -> str.toLowerCase(Locale.ROOT)); - FormatterStep lowercaseSometimes = PipeStepPair.named("lowercaseSometimes").openClose("", "") - .buildStepWhichAppliesSubSteps(Paths.get(""), Arrays.asList(lowercase)); - StepHarness.forSteps(lowercaseSometimes).test( + void andApply() { + FormatterStep fence = FenceStep.named("lowercaseSometimes").openClose("", "") + .applyWithin(Arrays.asList( + FormatterStep.createNeverUpToDate("lowercase", str -> str.toLowerCase(Locale.ROOT)))); + StepHarness.forSteps(fence).test( StringPrinter.buildStringFromLines( "A B C", "", diff --git a/testlib/src/test/java/com/diffplug/spotless/json/JsonSimpleStepTest.java b/testlib/src/test/java/com/diffplug/spotless/json/JsonSimpleStepTest.java index b4f98e69ba..19edc1a8e8 100644 --- a/testlib/src/test/java/com/diffplug/spotless/json/JsonSimpleStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/json/JsonSimpleStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,48 +19,115 @@ import org.junit.jupiter.api.Test; -import com.diffplug.spotless.*; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.SerializableEqualityTester; +import com.diffplug.spotless.StepHarness; +import com.diffplug.spotless.TestProvisioner; -class JsonSimpleStepTest extends JsonFormatterStepCommonTests { +class JsonSimpleStepTest { + + private static final int INDENT = 4; + + private final FormatterStep step = JsonSimpleStep.create(INDENT, TestProvisioner.mavenCentral()); + private final StepHarness stepHarness = StepHarness.forStep(step); + + @Test + void cannotProvidedNullProvisioner() { + assertThatThrownBy(() -> JsonSimpleStep.create(INDENT, null)).isInstanceOf(NullPointerException.class).hasMessage("provisioner cannot be null"); + } + + @Test + void handlesSingletonObject() { + doWithResource(stepHarness, "singletonObject"); + } + + @Test + void handlesSingletonObjectWithArray() { + doWithResource(stepHarness, "singletonObjectWithArray"); + } + + @Test + void handlesNestedObject() { + doWithResource(stepHarness, "nestedObject"); + } @Test - void handlesSingletonObject() throws Exception { - doWithResource("singletonObject"); + void handlesSingletonArray() { + doWithResource(stepHarness, "singletonArray"); } @Test - void handlesSingletonObjectWithArray() throws Exception { - doWithResource("singletonObjectWithArray"); + void handlesEmptyFile() { + doWithResource(stepHarness, "empty"); } @Test - void handlesComplexNestedObject() throws Exception { - doWithResource("cucumberJsonSample"); + void handlesComplexNestedObject() { + doWithResource(stepHarness, "cucumberJsonSample"); } @Test - void handlesObjectWithNull() throws Exception { - doWithResource("objectWithNull"); + void handlesObjectWithNull() { + doWithResource(stepHarness, "objectWithNull"); } @Test void handlesInvalidJson() { - assertThatThrownBy(() -> doWithResource("invalidJson")) - .isInstanceOf(AssertionError.class) - .hasMessage("Unable to format JSON") - .hasRootCauseMessage("Expected a ',' or '}' at 9 [character 0 line 3]"); + stepHarness.testResourceExceptionMsg("json/invalidJsonBefore.json") + .contains("Expected a ',' or '}' at 9 [character 0 line 3]"); } @Test void handlesNotJson() { - assertThatThrownBy(() -> doWithResource("notJson")) - .isInstanceOf(AssertionError.class) - .hasMessage("Unable to determine JSON type, expected a '{' or '[' but found '#'") - .hasNoCause(); + stepHarness.testResourceExceptionMsg("json/notJsonBefore.json") + .contains("Unable to determine JSON type, expected a '{' or '[' but found '#'"); + } + + @Test + void canSetCustomIndentationLevel() { + FormatterStep step = JsonSimpleStep.create(6, TestProvisioner.mavenCentral()); + StepHarness stepHarness = StepHarness.forStep(step); + + String before = "json/singletonArrayBefore.json"; + String after = "json/singletonArrayAfter6Spaces.json"; + stepHarness.testResource(before, after); + } + + @Test + void canSetIndentationLevelTo0() { + FormatterStep step = JsonSimpleStep.create(0, TestProvisioner.mavenCentral()); + StepHarness stepHarness = StepHarness.forStep(step); + + String before = "json/singletonArrayBefore.json"; + String after = "json/singletonArrayAfter0Spaces.json"; + stepHarness.testResource(before, after); + } + + @Test + void equality() { + new SerializableEqualityTester() { + int spaces = 0; + + @Override + protected void setupTest(API api) { + // no changes, are the same + api.areDifferentThan(); + + // with different spacing + spaces = 1; + api.areDifferentThan(); + } + + @Override + protected FormatterStep create() { + return JsonSimpleStep.create(spaces, TestProvisioner.mavenCentral()); + } + }.testEquals(); } - @Override - protected FormatterStep createFormatterStep(int indent, Provisioner provisioner) { - return JsonSimpleStep.create(indent, provisioner); + private static void doWithResource(StepHarness stepHarness, String name) { + String before = String.format("json/%sBefore.json", name); + String after = String.format("json/%sAfter.json", name); + stepHarness.testResource(before, after); } } diff --git a/testlib/src/test/java/com/diffplug/spotless/json/gson/GsonStepTest.java b/testlib/src/test/java/com/diffplug/spotless/json/gson/GsonStepTest.java index cf1fd10752..66d4252d6d 100644 --- a/testlib/src/test/java/com/diffplug/spotless/json/gson/GsonStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/json/gson/GsonStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 DiffPlug + * Copyright 2022-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,8 +15,6 @@ */ package com.diffplug.spotless.json.gson; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - import org.junit.jupiter.api.Test; import com.diffplug.spotless.FormatterStep; @@ -41,25 +39,18 @@ void handlesObjectWithNull() throws Exception { @Test void handlesInvalidJson() { - assertThatThrownBy(() -> doWithResource("invalidJson")) - .isInstanceOf(AssertionError.class) - .hasMessage("Unable to format JSON") - .hasRootCauseMessage("End of input at line 3 column 1 path $.a"); + getStepHarness().testResourceExceptionMsg("json/invalidJsonBefore.json").isEqualTo("End of input at line 3 column 1 path $.a"); } @Test void handlesNotJson() { - assertThatThrownBy(() -> doWithResource("notJson")) - .isInstanceOf(AssertionError.class) - .hasMessage("Unable to format JSON") - .hasNoCause(); + getStepHarness().testResourceExceptionMsg("json/notJsonBefore.json").isEqualTo("Unable to format JSON"); } @Test void handlesSortingWhenSortByKeyEnabled() throws Exception { FormatterStep step = GsonStep.create(INDENT, true, false, DEFAULT_VERSION, TestProvisioner.mavenCentral()); - StepHarness stepHarness = StepHarness.forStep(step); - stepHarness.testResource("json/sortByKeysBefore.json", "json/sortByKeysAfter.json"); + StepHarness.forStep(step).testResource("json/sortByKeysBefore.json", "json/sortByKeysAfter.json"); } @Test @@ -86,10 +77,8 @@ void writesRawHtmlWhenHtmlEscapeDisabled() throws Exception { @Test void handlesVersionIncompatibility() { FormatterStep step = GsonStep.create(INDENT, false, false, "1.7", TestProvisioner.mavenCentral()); - StepHarness stepHarness = StepHarness.forStep(step); - assertThatThrownBy(() -> stepHarness.testResource("json/cucumberJsonSampleGsonBefore.json", "json/cucumberJsonSampleGsonAfter.json")) - .isInstanceOf(IllegalStateException.class) - .hasMessage("There was a problem interacting with Gson; maybe you set an incompatible version?"); + StepHarness.forStep(step).testResourceExceptionMsg("json/cucumberJsonSampleGsonBefore.json") + .isEqualTo("There was a problem interacting with Gson; maybe you set an incompatible version?"); } @Override diff --git a/testlib/src/test/java/com/diffplug/spotless/kotlin/DiktatStepTest.java b/testlib/src/test/java/com/diffplug/spotless/kotlin/DiktatStepTest.java index 45bf6524f1..5055f47e8d 100644 --- a/testlib/src/test/java/com/diffplug/spotless/kotlin/DiktatStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/kotlin/DiktatStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,27 +25,19 @@ import com.diffplug.spotless.FileSignature; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.ResourceHarness; -import com.diffplug.spotless.StepHarness; +import com.diffplug.spotless.StepHarnessWithFile; import com.diffplug.spotless.TestProvisioner; class DiktatStepTest extends ResourceHarness { @Test - void behavior() throws Exception { + void behavior() { FormatterStep step = DiktatStep.create(TestProvisioner.mavenCentral()); - StepHarness.forStep(step) - .testResourceException("kotlin/diktat/Unsolvable.kt", assertion -> { - assertion.isInstanceOf(AssertionError.class); - assertion.hasMessage("There are 4 unfixed errors:" + - System.lineSeparator() + "Error on line: 1, column: 1 cannot be fixed automatically" + - System.lineSeparator() + "[FILE_NAME_INCORRECT] file name is incorrect - it should end with .kt extension and be in PascalCase: testlib" + - System.lineSeparator() + "Error on line: 1, column: 1 cannot be fixed automatically" + - System.lineSeparator() + "[FILE_NAME_MATCH_CLASS] file name is incorrect - it should match with the class described in it if there is the only one class declared: testlib vs Unsolvable" + - System.lineSeparator() + "Error on line: 1, column: 1 cannot be fixed automatically" + - System.lineSeparator() + "[DEBUG_PRINT] use a dedicated logging library: found println()" + - System.lineSeparator() + "Error on line: 13, column: 9 cannot be fixed automatically" + - System.lineSeparator() + "[DEBUG_PRINT] use a dedicated logging library: found println()"); - }); + StepHarnessWithFile.forStep(this, step).testResourceExceptionMsg("kotlin/diktat/Unsolvable.kt").isEqualTo("There are 2 unfixed errors:" + + System.lineSeparator() + "Error on line: 1, column: 1 cannot be fixed automatically" + + System.lineSeparator() + "[DEBUG_PRINT] use a dedicated logging library: found println()" + + System.lineSeparator() + "Error on line: 13, column: 9 cannot be fixed automatically" + + System.lineSeparator() + "[DEBUG_PRINT] use a dedicated logging library: found println()"); } @Test @@ -55,19 +47,11 @@ void behaviorConf() throws Exception { FileSignature config = signAsList(conf); FormatterStep step = DiktatStep.create("1.2.1", TestProvisioner.mavenCentral(), config); - StepHarness.forStep(step) - .testResourceException("kotlin/diktat/Unsolvable.kt", assertion -> { - assertion.isInstanceOf(AssertionError.class); - assertion.hasMessage("There are 4 unfixed errors:" + - System.lineSeparator() + "Error on line: 1, column: 1 cannot be fixed automatically" + - System.lineSeparator() + "[FILE_NAME_INCORRECT] file name is incorrect - it should end with .kt extension and be in PascalCase: testlib" + - System.lineSeparator() + "Error on line: 1, column: 1 cannot be fixed automatically" + - System.lineSeparator() + "[FILE_NAME_MATCH_CLASS] file name is incorrect - it should match with the class described in it if there is the only one class declared: testlib vs Unsolvable" + - System.lineSeparator() + "Error on line: 1, column: 1 cannot be fixed automatically" + - System.lineSeparator() + "[DEBUG_PRINT] use a dedicated logging library: found println()" + - System.lineSeparator() + "Error on line: 13, column: 9 cannot be fixed automatically" + - System.lineSeparator() + "[DEBUG_PRINT] use a dedicated logging library: found println()"); - }); + StepHarnessWithFile.forStep(this, step).testResourceExceptionMsg("kotlin/diktat/Unsolvable.kt").isEqualTo("There are 2 unfixed errors:" + + System.lineSeparator() + "Error on line: 1, column: 1 cannot be fixed automatically" + + System.lineSeparator() + "[DEBUG_PRINT] use a dedicated logging library: found println()" + + System.lineSeparator() + "Error on line: 13, column: 9 cannot be fixed automatically" + + System.lineSeparator() + "[DEBUG_PRINT] use a dedicated logging library: found println()"); } @Test diff --git a/testlib/src/test/java/com/diffplug/spotless/kotlin/KtLintStepTest.java b/testlib/src/test/java/com/diffplug/spotless/kotlin/KtLintStepTest.java index 42e377fde5..54878864c3 100644 --- a/testlib/src/test/java/com/diffplug/spotless/kotlin/KtLintStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/kotlin/KtLintStepTest.java @@ -22,33 +22,30 @@ import com.diffplug.spotless.ResourceHarness; import com.diffplug.spotless.SerializableEqualityTester; import com.diffplug.spotless.StepHarness; +import com.diffplug.spotless.StepHarnessWithFile; import com.diffplug.spotless.TestProvisioner; class KtLintStepTest extends ResourceHarness { @Test void behavior() throws Exception { FormatterStep step = KtLintStep.create(TestProvisioner.mavenCentral()); - StepHarness.forStep(step) + StepHarnessWithFile.forStep(this, step) .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean") - .testResourceException("kotlin/ktlint/unsolvable.dirty", assertion -> { - assertion.isInstanceOf(AssertionError.class); - assertion.hasMessage("Error on line: 1, column: 1\n" + - "rule: no-wildcard-imports\n" + - "Wildcard import"); - }); + .testResourceExceptionMsg("kotlin/ktlint/unsolvable.dirty").isEqualTo( + "Error on line: 1, column: 1\n" + + "rule: no-wildcard-imports\n" + + "Wildcard import"); } @Test void worksShyiko() throws Exception { FormatterStep step = KtLintStep.create("0.31.0", TestProvisioner.mavenCentral()); - StepHarness.forStep(step) + StepHarnessWithFile.forStep(this, step) .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean") - .testResourceException("kotlin/ktlint/unsolvable.dirty", assertion -> { - assertion.isInstanceOf(AssertionError.class); - assertion.hasMessage("Error on line: 1, column: 1\n" + - "rule: no-wildcard-imports\n" + - "Wildcard import"); - }); + .testResourceExceptionMsg("kotlin/ktlint/unsolvable.dirty").isEqualTo( + "Error on line: 1, column: 1\n" + + "rule: no-wildcard-imports\n" + + "Wildcard import"); } // Regression test to ensure it works on the version it switched to Pinterest (version 0.32.0) @@ -57,14 +54,11 @@ void worksShyiko() throws Exception { @Test void worksPinterestAndPre034() throws Exception { FormatterStep step = KtLintStep.create("0.32.0", TestProvisioner.mavenCentral()); - StepHarness.forStep(step) + StepHarnessWithFile.forStep(this, step) .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean") - .testResourceException("kotlin/ktlint/unsolvable.dirty", assertion -> { - assertion.isInstanceOf(AssertionError.class); - assertion.hasMessage("Error on line: 1, column: 1\n" + - "rule: no-wildcard-imports\n" + - "Wildcard import"); - }); + .testResourceExceptionMsg("kotlin/ktlint/unsolvable.dirty").isEqualTo("Error on line: 1, column: 1\n" + + "rule: no-wildcard-imports\n" + + "Wildcard import"); } // Regression test to handle alpha and 1.x version numbers @@ -108,53 +102,41 @@ void works0_45_2() throws Exception { @Test void works0_46_0() throws Exception { FormatterStep step = KtLintStep.create("0.46.0", TestProvisioner.mavenCentral()); - StepHarness.forStep(step) + StepHarnessWithFile.forStep(this, step) .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean") - .testResourceException("kotlin/ktlint/unsolvable.dirty", assertion -> { - assertion.isInstanceOf(AssertionError.class); - assertion.hasMessage("Error on line: 1, column: 1\n" + - "rule: no-wildcard-imports\n" + - "Wildcard import"); - }); + .testResourceExceptionMsg("kotlin/ktlint/unsolvable.dirty").isEqualTo("Error on line: 1, column: 1\n" + + "rule: no-wildcard-imports\n" + + "Wildcard import"); } @Test void works0_47_0() throws Exception { FormatterStep step = KtLintStep.create("0.47.0", TestProvisioner.mavenCentral()); - StepHarness.forStep(step) + StepHarnessWithFile.forStep(this, step) .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean") - .testResourceException("kotlin/ktlint/unsolvable.dirty", assertion -> { - assertion.isInstanceOf(AssertionError.class); - assertion.hasMessage("Error on line: 1, column: 1\n" + - "rule: no-wildcard-imports\n" + - "Wildcard import"); - }); + .testResourceExceptionMsg("kotlin/ktlint/unsolvable.dirty").isEqualTo("Error on line: 1, column: 1\n" + + "rule: no-wildcard-imports\n" + + "Wildcard import"); } @Test void works0_47_1() throws Exception { FormatterStep step = KtLintStep.create("0.47.1", TestProvisioner.mavenCentral()); - StepHarness.forStep(step) + StepHarnessWithFile.forStep(this, step) .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean") - .testResourceException("kotlin/ktlint/unsolvable.dirty", assertion -> { - assertion.isInstanceOf(AssertionError.class); - assertion.hasMessage("Error on line: 1, column: 1\n" + - "rule: no-wildcard-imports\n" + - "Wildcard import"); - }); + .testResourceExceptionMsg("kotlin/ktlint/unsolvable.dirty").isEqualTo("Error on line: 1, column: 1\n" + + "rule: no-wildcard-imports\n" + + "Wildcard import"); } @Test void works0_48_0() throws Exception { FormatterStep step = KtLintStep.create("0.48.0", TestProvisioner.mavenCentral()); - StepHarness.forStep(step) + StepHarnessWithFile.forStep(this, step) .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean") - .testResourceException("kotlin/ktlint/unsolvable.dirty", assertion -> { - assertion.isInstanceOf(AssertionError.class); - assertion.hasMessage("Error on line: 1, column: 1\n" + - "rule: no-wildcard-imports\n" + - "Wildcard import"); - }); + .testResourceExceptionMsg("kotlin/ktlint/unsolvable.dirty").isEqualTo("Error on line: 1, column: 1\n" + + "rule: no-wildcard-imports\n" + + "Wildcard import"); } @Test diff --git a/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java b/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java index d2d866e97d..5f14022ad8 100644 --- a/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.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. @@ -25,13 +25,14 @@ import com.diffplug.common.collect.ImmutableMap; import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.ResourceHarness; import com.diffplug.spotless.StepHarness; import com.diffplug.spotless.StepHarnessWithFile; import com.diffplug.spotless.TestProvisioner; import com.diffplug.spotless.tag.NpmTest; @NpmTest -class PrettierFormatterStepTest { +class PrettierFormatterStepTest extends ResourceHarness { @NpmTest @Nested @@ -96,8 +97,8 @@ void parserInferenceBasedOnFilenameIsWorking() throws Exception { npmPathResolver(), new PrettierConfig(null, Collections.emptyMap())); - try (StepHarnessWithFile stepHarness = StepHarnessWithFile.forStep(formatterStep)) { - stepHarness.testResource(new File("test.json"), dirtyFile, cleanFile); + try (StepHarnessWithFile stepHarness = StepHarnessWithFile.forStep(this, formatterStep)) { + stepHarness.testResource("test.json", dirtyFile, cleanFile); } } @@ -109,11 +110,9 @@ void verifyPrettierErrorMessageIsRelayed() throws Exception { buildDir(), npmPathResolver(), new PrettierConfig(null, ImmutableMap.of("parser", "postcss"))); - try (StepHarness stepHarness = StepHarness.forStep(formatterStep)) { - stepHarness.testResourceException("npm/prettier/filetypes/scss/scss.dirty", exception -> { - exception.hasMessageContaining("HTTP 501"); - exception.hasMessageContaining("Couldn't resolve parser \"postcss\""); - }); + try (StepHarnessWithFile stepHarness = StepHarnessWithFile.forStep(this, formatterStep)) { + stepHarness.testResourceExceptionMsg("npm/prettier/filetypes/scss/scss.dirty").isEqualTo( + "Unexpected response status code at /prettier/format [HTTP 501] -- (Error while formatting: Error: Couldn't resolve parser \"postcss\")"); } } } diff --git a/testlib/src/test/java/com/diffplug/spotless/scala/ScalaFmtStepTest.java b/testlib/src/test/java/com/diffplug/spotless/scala/ScalaFmtStepTest.java index bad573e985..0f0e021eeb 100644 --- a/testlib/src/test/java/com/diffplug/spotless/scala/ScalaFmtStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/scala/ScalaFmtStepTest.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,12 +15,8 @@ */ package com.diffplug.spotless.scala; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - import java.io.File; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import org.junit.jupiter.api.Test; @@ -61,7 +57,7 @@ void behaviorCustomConfigVersion_3_0_0() throws Exception { @Test void equality() throws Exception { new SerializableEqualityTester() { - String version = "3.5.9"; + String version = "3.6.1"; File configFile = null; @Override @@ -69,7 +65,7 @@ protected void setupTest(API api) throws IOException { // same version == same api.areDifferentThan(); // change the version, and it's different - version = "3.5.8"; + version = "3.0.0"; api.areDifferentThan(); // add a config file, and its different configFile = createTestFile("scala/scalafmt/scalafmt.conf"); @@ -90,9 +86,7 @@ protected FormatterStep create() { void invalidConfiguration() throws Exception { File invalidConfFile = createTestFile("scala/scalafmt/scalafmt.invalid.conf"); Provisioner provisioner = TestProvisioner.mavenCentral(); - - InvocationTargetException exception = assertThrows(InvocationTargetException.class, - () -> StepHarness.forStep(ScalaFmtStep.create("3.0.0", provisioner, invalidConfFile)).test("", "")); - assertThat(exception.getCause().getMessage()).contains("found option 'invalidScalaFmtConfigField' which wasn't expected"); + StepHarness.forStep(ScalaFmtStep.create("3.0.0", provisioner, invalidConfFile)) + .testExceptionMsg("").contains("found option 'invalidScalaFmtConfigField' which wasn't expected"); } }