diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/java/EclipseFormatterStepTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/java/EclipseFormatterStepTest.java index 4202ac2c9e..e3c9182af2 100644 --- a/lib-extra/src/test/java/com/diffplug/spotless/extra/java/EclipseFormatterStepTest.java +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/java/EclipseFormatterStepTest.java @@ -22,7 +22,7 @@ import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.ResourceHarness; -import com.diffplug.spotless.StepEqualityTester; +import com.diffplug.spotless.SerializableEqualityTester; import com.diffplug.spotless.StepHarness; import com.diffplug.spotless.TestProvisioner; @@ -54,17 +54,15 @@ public void longLiteralProblem() throws Throwable { public void equality() throws IOException { File xmlFile = createTestFile("java/eclipse/format/formatter.xml"); File propFile = createTestFile("java/eclipse/format/formatter.properties"); - new StepEqualityTester() { + new SerializableEqualityTester() { File settingsFile; @Override protected void setupTest(API api) { settingsFile = xmlFile; - api.assertThisEqualToThis(); api.areDifferentThan(); settingsFile = propFile; - api.assertThisEqualToThis(); api.areDifferentThan(); } diff --git a/lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicy.java b/lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicy.java new file mode 100644 index 0000000000..4d3555854b --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicy.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless; + +import java.io.Serializable; + +/** A policy for handling exceptions in the format. */ +public interface FormatExceptionPolicy extends Serializable, NoLambda { + /** Called for every error in the formatter. */ + void handleError(Throwable e, FormatterStep step, String relativePath); + + /** + * Returns a byte array representation of everything inside this `FormatExceptionPolicy`. + * + * The main purpose of this method is to ensure one can't instantiate this class with lambda + * expressions, which are notoriously difficult to serialize and deserialize properly. + */ + public byte[] toBytes(); + + /** + * A policy which rethrows subclasses of `Error` and logs other kinds of Exception. + */ + public static FormatExceptionPolicy failOnlyOnError() { + return new FormatExceptionPolicyLegacy(); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicyLegacy.java b/lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicyLegacy.java new file mode 100644 index 0000000000..df95542a44 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicyLegacy.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless; + +import java.util.logging.Level; +import java.util.logging.Logger; + +class FormatExceptionPolicyLegacy extends NoLambda.EqualityBasedOnSerialization implements FormatExceptionPolicy { + private static final long serialVersionUID = 1L; + + private static final Logger logger = Logger.getLogger(Formatter.class.getName()); + + @Override + public void handleError(Throwable e, FormatterStep step, String relativePath) { + if (e instanceof Error) { + error(e, step, relativePath); + throw ((Error) e); + } else { + warning(e, step, relativePath); + } + } + + static void error(Throwable e, FormatterStep step, String relativePath) { + logger.log(Level.SEVERE, "Step '" + step.getName() + "' found problem in '" + relativePath + "':\n" + e.getMessage(), e); + } + + static void warning(Throwable e, FormatterStep step, String relativePath) { + logger.log(Level.WARNING, "Unable to apply step '" + step.getName() + "' to '" + relativePath + "'", e); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicyStrict.java b/lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicyStrict.java new file mode 100644 index 0000000000..ec01ecf7d0 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicyStrict.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless; + +import java.util.Set; +import java.util.TreeSet; + +/** + * A policy for handling exceptions in the format. Any exceptions will + * halt the build except for a specifically excluded path or step. + */ +public class FormatExceptionPolicyStrict extends NoLambda.EqualityBasedOnSerialization implements FormatExceptionPolicy { + private static final long serialVersionUID = 1L; + + private final Set excludeSteps = new TreeSet<>(); + private final Set excludePaths = new TreeSet<>(); + + /** Adds a step name to exclude. */ + public void excludeStep(String stepName) { + excludeSteps.add(stepName); + } + + /** Adds a realtive pathx to exclude. */ + public void excludePath(String relativePath) { + excludePaths.add(relativePath); + } + + @Override + public void handleError(Throwable e, FormatterStep step, String relativePath) { + if (excludeSteps.contains(step.getName())) { + FormatExceptionPolicyLegacy.warning(e, step, relativePath); + } else { + if (excludePaths.contains(relativePath)) { + FormatExceptionPolicyLegacy.warning(e, step, relativePath); + } else { + FormatExceptionPolicyLegacy.error(e, step, relativePath); + throw ThrowingEx.asRuntimeRethrowError(e); + } + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/Formatter.java b/lib/src/main/java/com/diffplug/spotless/Formatter.java index ea322a1d04..101fdb4084 100644 --- a/lib/src/main/java/com/diffplug/spotless/Formatter.java +++ b/lib/src/main/java/com/diffplug/spotless/Formatter.java @@ -17,33 +17,63 @@ import java.io.File; import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +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 java.util.logging.Level; -import java.util.logging.Logger; import javax.annotation.Nullable; /** Formatter which performs the full formatting. */ -public final class Formatter { - private final LineEnding.Policy lineEndingsPolicy; - private final Charset encoding; - private final Path rootDir; - private final List steps; +public final class Formatter implements Serializable { + private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(Formatter.class.getName()); + 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) { + private Formatter(LineEnding.Policy lineEndingsPolicy, Charset encoding, Path rootDirectory, List steps, FormatExceptionPolicy exceptionPolicy) { this.lineEndingsPolicy = Objects.requireNonNull(lineEndingsPolicy, "lineEndingsPolicy"); this.encoding = Objects.requireNonNull(encoding, "encoding"); this.rootDir = Objects.requireNonNull(rootDirectory, "rootDir"); this.steps = new ArrayList<>(Objects.requireNonNull(steps, "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 + @SuppressWarnings("unchecked") + 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 + @SuppressWarnings("unused") + private void readObjectNoData() throws ObjectStreamException { + throw new UnsupportedOperationException(); } public LineEnding.Policy getLineEndingsPolicy() { @@ -62,6 +92,10 @@ public List getSteps() { return steps; } + public FormatExceptionPolicy getExceptionPolicy() { + return exceptionPolicy; + } + public static Formatter.Builder builder() { return new Formatter.Builder(); } @@ -72,6 +106,7 @@ public static class Builder { private Charset encoding; private Path rootDir; private List steps; + private FormatExceptionPolicy exceptionPolicy; private Builder() {} @@ -95,8 +130,14 @@ public Builder steps(List steps) { return this; } + public Builder exceptionPolicy(FormatExceptionPolicy exceptionPolicy) { + this.exceptionPolicy = exceptionPolicy; + return this; + } + public Formatter build() { - return new Formatter(lineEndingsPolicy, encoding, rootDir, steps); + return new Formatter(lineEndingsPolicy, encoding, rootDir, steps, + exceptionPolicy == null ? FormatExceptionPolicy.failOnlyOnError() : exceptionPolicy); } } @@ -183,11 +224,9 @@ public String compute(String unix, File file) throws Error { // Should already be unix-only, but some steps might misbehave. unix = LineEnding.toUnix(formatted); } - } catch (Error e) { - logger.severe("Step '" + step.getName() + "' found problem in '" + rootDir.relativize(file.toPath()) + "':\n" + e.getMessage()); - throw e; } catch (Throwable e) { - logger.log(Level.WARNING, "Unable to apply step '" + step.getName() + "' to '" + rootDir.relativize(file.toPath()), e); + String relativePath = rootDir.relativize(file.toPath()).toString(); + exceptionPolicy.handleError(e, step, relativePath); } } return unix; @@ -201,6 +240,7 @@ public int hashCode() { result = prime * result + lineEndingsPolicy.hashCode(); result = prime * result + rootDir.hashCode(); result = prime * result + steps.hashCode(); + result = prime * result + exceptionPolicy.hashCode(); return result; } @@ -219,6 +259,7 @@ public boolean equals(Object obj) { return encoding.equals(other.encoding) && lineEndingsPolicy.equals(other.lineEndingsPolicy) && rootDir.equals(other.rootDir) && - steps.equals(other.steps); + steps.equals(other.steps) && + exceptionPolicy.equals(other.exceptionPolicy); } } diff --git a/lib/src/main/java/com/diffplug/spotless/LazyForwardingEquality.java b/lib/src/main/java/com/diffplug/spotless/LazyForwardingEquality.java index 195be858fd..05e758d586 100644 --- a/lib/src/main/java/com/diffplug/spotless/LazyForwardingEquality.java +++ b/lib/src/main/java/com/diffplug/spotless/LazyForwardingEquality.java @@ -31,7 +31,7 @@ * of lazily-computed state. The state's serialized form is used to implement * equals() and hashCode(), so you don't have to. */ -public abstract class LazyForwardingEquality implements Serializable { +public abstract class LazyForwardingEquality implements Serializable, NoLambda { private static final long serialVersionUID = 1L; /** Lazily initialized - null indicates that the state has not yet been set. */ @@ -80,13 +80,18 @@ private void readObjectNoData() throws ObjectStreamException { throw new UnsupportedOperationException(); } + @Override + public byte[] toBytes() { + return toBytes(state()); + } + @Override public final boolean equals(Object other) { if (other == null) { return false; } else if (getClass().equals(other.getClass())) { - Serializable otherState = ((LazyForwardingEquality) other).state(); - return Arrays.equals(toBytes(otherState), toBytes(state())); + LazyForwardingEquality otherCast = (LazyForwardingEquality) other; + return Arrays.equals(otherCast.toBytes(), toBytes()); } else { return false; } @@ -94,7 +99,7 @@ public final boolean equals(Object other) { @Override public final int hashCode() { - return Arrays.hashCode(toBytes(state())); + return Arrays.hashCode(toBytes()); } static byte[] toBytes(Serializable obj) { diff --git a/lib/src/main/java/com/diffplug/spotless/LineEnding.java b/lib/src/main/java/com/diffplug/spotless/LineEnding.java index a05c0ecb01..ea70664e0f 100644 --- a/lib/src/main/java/com/diffplug/spotless/LineEnding.java +++ b/lib/src/main/java/com/diffplug/spotless/LineEnding.java @@ -76,10 +76,25 @@ public Policy createPolicy() { } } - private static final Policy WINDOWS_POLICY = file -> WINDOWS.str(); - private static final Policy UNIX_POLICY = file -> UNIX.str(); + static class ConstantLineEndingPolicy extends NoLambda.EqualityBasedOnSerialization implements Policy { + private static final long serialVersionUID = 1L; + + final String lineEnding; + + ConstantLineEndingPolicy(String lineEnding) { + this.lineEnding = lineEnding; + } + + @Override + public String getEndingFor(File file) { + return lineEnding; + } + } + + private static final Policy WINDOWS_POLICY = new ConstantLineEndingPolicy(WINDOWS.str()); + private static final Policy UNIX_POLICY = new ConstantLineEndingPolicy(UNIX.str()); private static final String _platformNative = System.getProperty("line.separator"); - private static final Policy _platformNativePolicy = file -> _platformNative; + private static final Policy _platformNativePolicy = new ConstantLineEndingPolicy(_platformNative); /** Returns the standard line ending for this policy. */ public String str() { @@ -93,7 +108,7 @@ public String str() { // @formatter:on /** A policy for line endings which can vary based on the specific file being requested. */ - public interface Policy extends Serializable { + public interface Policy extends Serializable, NoLambda { /** Returns the line ending appropriate for the given file. */ String getEndingFor(File file); diff --git a/lib/src/main/java/com/diffplug/spotless/NoLambda.java b/lib/src/main/java/com/diffplug/spotless/NoLambda.java new file mode 100644 index 0000000000..6ce14d914d --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/NoLambda.java @@ -0,0 +1,71 @@ +/* + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless; + +import java.io.Serializable; +import java.util.Arrays; + +/** + * Marker interface to prevent lambda implementations of + * single-method interfaces that require serializability. + * + * In order for Spotless to support up-to-date checks, all + * of its parameters must be {@link Serializable} so that + * entries can be written to file, and they must implement + * equals and hashCode correctly. + * + * This interface and its standard implementation, + * {@link EqualityBasedOnSerialization}, are a quick way + * to accomplish these goals. + */ +public interface NoLambda extends Serializable { + /** + * Returns a byte array representation of everything inside this `SerializableFileFilter`. + * + * The main purpose of this method is to ensure one can't instantiate this class with lambda + * expressions, which are notoriously difficult to serialize and deserialize properly. (See + * `SerializableFileFilterImpl.SkipFilesNamed` for an example of how to make a serializable + * subclass.) + */ + public byte[] toBytes(); + + /** An implementation of NoLambda in which equality is based on the serialized representation of itself. */ + public static abstract class EqualityBasedOnSerialization implements NoLambda { + private static final long serialVersionUID = 1733798699224768949L; + + @Override + public byte[] toBytes() { + return LazyForwardingEquality.toBytes(this); + } + + @Override + public int hashCode() { + return Arrays.hashCode(toBytes()); + } + + @Override + public boolean equals(Object otherObj) { + if (otherObj == null) { + return false; + } else if (otherObj.getClass().equals(this.getClass())) { + EqualityBasedOnSerialization other = (EqualityBasedOnSerialization) otherObj; + return Arrays.equals(toBytes(), other.toBytes()); + } else { + return false; + } + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/SerializableFileFilter.java b/lib/src/main/java/com/diffplug/spotless/SerializableFileFilter.java index c6325af62c..27561758fe 100644 --- a/lib/src/main/java/com/diffplug/spotless/SerializableFileFilter.java +++ b/lib/src/main/java/com/diffplug/spotless/SerializableFileFilter.java @@ -17,49 +17,11 @@ import java.io.FileFilter; import java.io.Serializable; -import java.util.Arrays; /** A file filter with full support for serialization. */ -public interface SerializableFileFilter extends FileFilter, Serializable { +public interface SerializableFileFilter extends FileFilter, Serializable, NoLambda { /** Creates a FileFilter which will accept all files except files with the given name. */ public static SerializableFileFilter skipFilesNamed(String name) { return new SerializableFileFilterImpl.SkipFilesNamed(name); } - - /** - * Returns a byte array representation of everything inside this `SerializableFileFilter`. - * - * The main purpose of this method is to ensure one can't instantiate this class with lambda - * expressions, which are notoriously difficult to serialize and deserialize properly. (See - * `SerializableFileFilterImpl.SkipFilesNamed` for an example of how to make a serializable - * subclass.) - */ - public byte[] toBytes(); - - /** An implementation of SerializableFileFilter in which equality is based on the serialized representation. */ - public static abstract class EqualityBasedOnSerialization implements SerializableFileFilter { - private static final long serialVersionUID = 1733798699224768949L; - - @Override - public byte[] toBytes() { - return LazyForwardingEquality.toBytes(this); - } - - @Override - public int hashCode() { - return Arrays.hashCode(toBytes()); - } - - @Override - public boolean equals(Object otherObj) { - if (otherObj == null) { - return false; - } else if (otherObj.getClass().equals(this.getClass())) { - EqualityBasedOnSerialization other = (EqualityBasedOnSerialization) otherObj; - return Arrays.equals(toBytes(), other.toBytes()); - } else { - return false; - } - } - } } diff --git a/lib/src/main/java/com/diffplug/spotless/SerializableFileFilterImpl.java b/lib/src/main/java/com/diffplug/spotless/SerializableFileFilterImpl.java index d46aee6e6a..82b935839f 100644 --- a/lib/src/main/java/com/diffplug/spotless/SerializableFileFilterImpl.java +++ b/lib/src/main/java/com/diffplug/spotless/SerializableFileFilterImpl.java @@ -19,7 +19,7 @@ import java.util.Objects; class SerializableFileFilterImpl { - static class SkipFilesNamed extends SerializableFileFilter.EqualityBasedOnSerialization { + static class SkipFilesNamed extends NoLambda.EqualityBasedOnSerialization implements SerializableFileFilter { private static final long serialVersionUID = 1L; private final String nameToSkip; diff --git a/lib/src/main/java/com/diffplug/spotless/ThrowingEx.java b/lib/src/main/java/com/diffplug/spotless/ThrowingEx.java index c960ce776d..d92dce90a2 100644 --- a/lib/src/main/java/com/diffplug/spotless/ThrowingEx.java +++ b/lib/src/main/java/com/diffplug/spotless/ThrowingEx.java @@ -94,21 +94,31 @@ public static RuntimeException asRuntime(Exception e) { * try { * doSomething(); * } catch (Throwable e) { - * throw unwrapAsRuntimeOrRethrow(e); + * throw unwrapCause(e); * } * ``` */ - public static RuntimeException unwrapAsRuntimeOrRethrow(Throwable e) { + public static RuntimeException unwrapCause(Throwable e) { Throwable cause = e.getCause(); if (cause == null) { - return ifErrorRethrowElseAsRuntime(e); + return asRuntimeRethrowError(e); } else { - return ifErrorRethrowElseAsRuntime(cause); + return asRuntimeRethrowError(cause); } } - /** Rethrows errors, wraps and returns everything else as a runtime exception. */ - private static RuntimeException ifErrorRethrowElseAsRuntime(Throwable e) { + /** + * Rethrows errors, wraps and returns everything else as a runtime exception. + * + * try { + * doSomething(); + * } catch (Throwable e) { + * throw asRuntimeRethrowError(e); + * } + * ``` + * + * */ + static RuntimeException asRuntimeRethrowError(Throwable e) { if (e instanceof Error) { throw (Error) e; } else if (e instanceof RuntimeException) { diff --git a/lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java b/lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java index 2295205dcc..31ddef5473 100644 --- a/lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java +++ b/lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java @@ -97,7 +97,7 @@ FormatterFunc createFormat() throws Exception { String formatted = (String) formatterMethod.invoke(ktlint, input, ruleSets, formatterCallback); return formatted; } catch (InvocationTargetException e) { - throw ThrowingEx.unwrapAsRuntimeOrRethrow(e); + throw ThrowingEx.unwrapCause(e); } }; } diff --git a/lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java b/lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java index 95e65e2f69..4e7871003c 100644 --- a/lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java +++ b/lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java @@ -18,7 +18,9 @@ import java.io.Serializable; import java.lang.reflect.Method; import java.util.Map; +import java.util.NavigableMap; import java.util.Objects; +import java.util.TreeMap; import java.util.function.Consumer; import java.util.logging.Logger; @@ -60,11 +62,13 @@ private static class State implements Serializable { /** The jar that contains the eclipse formatter. */ final JarState jarState; - final Map properties; + final NavigableMap properties; State(JarState jarState, Map properties) { this.jarState = Objects.requireNonNull(jarState); - this.properties = Objects.requireNonNull(properties); + // because equality is computed based on serialization, it's important to order the properties + // before writing them + this.properties = new TreeMap<>(Objects.requireNonNull(properties)); } FormatterFunc createFormat() throws Exception { 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 16e3522183..5a3b27e0c6 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 @@ -33,6 +33,7 @@ import org.gradle.api.file.FileCollection; import org.gradle.api.internal.file.UnionFileCollection; +import com.diffplug.spotless.FormatExceptionPolicyStrict; import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.LazyForwardingEquality; @@ -105,6 +106,18 @@ public void setEncoding(Charset charset) { encoding = Objects.requireNonNull(charset); } + FormatExceptionPolicyStrict exceptionPolicy = new FormatExceptionPolicyStrict(); + + /** Ignores errors in the given step. */ + public void ignoreErrorForStep(String stepName) { + exceptionPolicy.excludeStep(stepName); + } + + /** Ignores errors for the given relative path. */ + public void ignoreErrorForPath(String relativePath) { + exceptionPolicy.excludePath(relativePath); + } + /** Sets encoding to use (defaults to {@link SpotlessExtension#getEncoding()}). */ public void encoding(String charset) { setEncoding(charset); @@ -340,6 +353,7 @@ public void licenseHeaderFile(Object licenseHeaderFile, String delimiter) { protected void setupTask(SpotlessTask task) { task.setPaddedCell(paddedCell); task.setEncoding(getEncoding().name()); + task.setExceptionPolicy(exceptionPolicy); task.setTarget(target); task.setSteps(steps); task.setLineEndingsPolicy(getLineEndings().createPolicy(getProject().getProjectDir(), () -> task.target)); 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 166969d34e..fb4bd85769 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 @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Objects; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; @@ -33,6 +34,8 @@ import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.incremental.IncrementalTaskInputs; +import com.diffplug.spotless.FormatExceptionPolicy; +import com.diffplug.spotless.FormatExceptionPolicyStrict; import com.diffplug.spotless.Formatter; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.LineEnding; @@ -75,6 +78,17 @@ public void setPaddedCell(boolean paddedCell) { this.paddedCell = paddedCell; } + @Input + protected FormatExceptionPolicy exceptionPolicy = new FormatExceptionPolicyStrict(); + + public void setExceptionPolicy(FormatExceptionPolicy exceptionPolicy) { + this.exceptionPolicy = Objects.requireNonNull(exceptionPolicy); + } + + public FormatExceptionPolicy getExceptionPolicy() { + return exceptionPolicy; + } + @InputFiles @SkipWhenEmpty protected Iterable target; diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ErrorShouldRethrow.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ErrorShouldRethrow.java index 0969d0c629..2eddba7fea 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ErrorShouldRethrow.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ErrorShouldRethrow.java @@ -15,7 +15,6 @@ */ package com.diffplug.gradle.spotless; -import java.io.File; import java.util.List; import java.util.stream.Collectors; @@ -33,7 +32,7 @@ public class ErrorShouldRethrow extends GradleIntegrationTest { @Test public void noSwearing() throws Exception { - File build = write("build.gradle", + write("build.gradle", "plugins {", " id 'com.diffplug.gradle.spotless'", "}", @@ -53,16 +52,7 @@ public void noSwearing() throws Exception { String expectedToStartWith = StringPrinter.buildStringFromLines( ":spotlessMiscStep 'no swearing' found problem in 'README.md':", "No swearing!", - " FAILED", - "", - "FAILURE: Build failed with an exception.", - "", - "* Where:", - "Build file '" + build + "' line: 9", - "", - "* What went wrong:", - "Execution failed for task ':spotlessMisc'.", - "> No swearing!"); + "java.lang.AssertionError: No swearing!").trim(); int numNewlines = CharMatcher.is('\n').countIn(expectedToStartWith); List actualLines = Splitter.on('\n').splitToList(LineEnding.toUnix(result.getOutput())); String actualStart = actualLines.subList(0, numNewlines + 1).stream().collect(Collectors.joining("\n")); diff --git a/testlib/src/main/java/com/diffplug/spotless/StepEqualityTester.java b/testlib/src/main/java/com/diffplug/spotless/SerializableEqualityTester.java similarity index 57% rename from testlib/src/main/java/com/diffplug/spotless/StepEqualityTester.java rename to testlib/src/main/java/com/diffplug/spotless/SerializableEqualityTester.java index 7ff94b8af7..096edf62e2 100644 --- a/testlib/src/main/java/com/diffplug/spotless/StepEqualityTester.java +++ b/testlib/src/main/java/com/diffplug/spotless/SerializableEqualityTester.java @@ -15,22 +15,22 @@ */ package com.diffplug.spotless; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; import com.diffplug.common.base.Box; import com.diffplug.common.testing.EqualsTester; -public abstract class StepEqualityTester { - protected abstract FormatterStep create(); +public abstract class SerializableEqualityTester { + protected abstract Serializable create(); protected abstract void setupTest(API api) throws Exception; public interface API { - void assertThis(); - - void assertThisEqualToThis(); - void areDifferentThan(); } @@ -38,21 +38,20 @@ public void testEquals() { List> allGroups = new ArrayList<>(); Box> currentGroup = Box.of(new ArrayList<>()); API api = new API() { - @Override - public void assertThis() { - currentGroup.get().add(create()); - } - - @Override - public void assertThisEqualToThis() { - assertThis(); - assertThis(); - } - @Override public void areDifferentThan() { - allGroups.add(currentGroup.get()); - currentGroup.set(new ArrayList<>()); + currentGroup.modify(current -> { + // create two instances, and add them to the group + current.add(create()); + current.add(create()); + // create two instances using a serialization roundtrip, and add them to the group + current.add(reserialize(create())); + current.add(reserialize(create())); + // add this group to the list of all groups + allGroups.add(current); + // and return a new blank group for the next call + return new ArrayList<>(); + }); } }; try { @@ -70,4 +69,15 @@ public void areDifferentThan() { } tester.testEquals(); } + + @SuppressWarnings("unchecked") + private static T reserialize(T input) { + byte[] asBytes = LazyForwardingEquality.toBytes(input); + ByteArrayInputStream byteInput = new ByteArrayInputStream(asBytes); + try (ObjectInputStream objectInput = new ObjectInputStream(byteInput)) { + return (T) objectInput.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw ThrowingEx.asRuntime(e); + } + } } diff --git a/testlib/src/test/java/com/diffplug/spotless/FilterByFileFormatterStepTest.java b/testlib/src/test/java/com/diffplug/spotless/FilterByFileFormatterStepTest.java index afe1b1709f..78155b67b3 100644 --- a/testlib/src/test/java/com/diffplug/spotless/FilterByFileFormatterStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/FilterByFileFormatterStepTest.java @@ -34,7 +34,7 @@ public void behavior() throws Exception { @Test public void equality() throws Exception { - new StepEqualityTester() { + new SerializableEqualityTester() { String state; String filter; @@ -43,19 +43,15 @@ protected void setupTest(API api) { // no filter, standard state state = "state"; filter = null; - api.assertThisEqualToThis(); api.areDifferentThan(); // same state, but now with a filter filter = "a"; - api.assertThisEqualToThis(); api.areDifferentThan(); // same state, but now with a filter filter = "b"; - api.assertThisEqualToThis(); api.areDifferentThan(); // same filter, but the state has changed state = "otherState"; - api.assertThisEqualToThis(); api.areDifferentThan(); } diff --git a/testlib/src/test/java/com/diffplug/spotless/FormatterTest.java b/testlib/src/test/java/com/diffplug/spotless/FormatterTest.java new file mode 100644 index 0000000000..02089058fa --- /dev/null +++ b/testlib/src/test/java/com/diffplug/spotless/FormatterTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016 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.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.Test; + +import com.diffplug.common.base.StandardSystemProperty; +import com.diffplug.spotless.generic.EndWithNewlineStep; + +public class FormatterTest { + @Test + public 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 { + api.areDifferentThan(); + + lineEndingsPolicy = LineEnding.WINDOWS.createPolicy(); + api.areDifferentThan(); + + 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 + 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/generic/EndWithNewlineStepTest.java b/testlib/src/test/java/com/diffplug/spotless/generic/EndWithNewlineStepTest.java index c0d9155616..d16e88ab05 100644 --- a/testlib/src/test/java/com/diffplug/spotless/generic/EndWithNewlineStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/generic/EndWithNewlineStepTest.java @@ -18,7 +18,7 @@ import org.junit.Test; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.StepEqualityTester; +import com.diffplug.spotless.SerializableEqualityTester; import com.diffplug.spotless.StepHarness; public class EndWithNewlineStepTest { @@ -35,10 +35,9 @@ public void behavior() throws Exception { @Test public void equality() throws Exception { - new StepEqualityTester() { + new SerializableEqualityTester() { @Override protected void setupTest(API api) { - api.assertThisEqualToThis(); api.areDifferentThan(); } diff --git a/testlib/src/test/java/com/diffplug/spotless/generic/IndentStepTest.java b/testlib/src/test/java/com/diffplug/spotless/generic/IndentStepTest.java index feb2aa12e7..30043d6f8f 100644 --- a/testlib/src/test/java/com/diffplug/spotless/generic/IndentStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/generic/IndentStepTest.java @@ -22,7 +22,7 @@ import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.ResourceHarness; -import com.diffplug.spotless.StepEqualityTester; +import com.diffplug.spotless.SerializableEqualityTester; import com.diffplug.spotless.generic.IndentStep; public class IndentStepTest extends ResourceHarness { @@ -59,25 +59,21 @@ public void doesntClipNewlines() throws Throwable { @Test public void equality() { - new StepEqualityTester() { + new SerializableEqualityTester() { IndentStep.Type type = IndentStep.Type.SPACE; int numSpacesPerTab = 2; @Override protected void setupTest(API api) { - api.assertThisEqualToThis(); api.areDifferentThan(); numSpacesPerTab = 4; - api.assertThisEqualToThis(); api.areDifferentThan(); type = IndentStep.Type.TAB; - api.assertThisEqualToThis(); api.areDifferentThan(); numSpacesPerTab = 2; - api.assertThisEqualToThis(); api.areDifferentThan(); } diff --git a/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java b/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java index 92552b1ff7..d66d37ecad 100644 --- a/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java @@ -23,7 +23,7 @@ import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.ResourceHarness; -import com.diffplug.spotless.StepEqualityTester; +import com.diffplug.spotless.SerializableEqualityTester; import com.diffplug.spotless.generic.LicenseHeaderStep; public class LicenseHeaderStepTest extends ResourceHarness { @@ -76,25 +76,21 @@ public void sanitizerDoesntGoTooFar() throws Throwable { @Test public void equality() { - new StepEqualityTester() { + new SerializableEqualityTester() { String header = "LICENSE"; String delimiter = "package"; @Override protected void setupTest(API api) { - api.assertThisEqualToThis(); api.areDifferentThan(); delimiter = "crate"; - api.assertThisEqualToThis(); api.areDifferentThan(); header = "APACHE"; - api.assertThisEqualToThis(); api.areDifferentThan(); delimiter = "package"; - api.assertThisEqualToThis(); api.areDifferentThan(); } diff --git a/testlib/src/test/java/com/diffplug/spotless/generic/TrimTrailingWhitespaceStepTest.java b/testlib/src/test/java/com/diffplug/spotless/generic/TrimTrailingWhitespaceStepTest.java index d39234f552..15d9415f9d 100644 --- a/testlib/src/test/java/com/diffplug/spotless/generic/TrimTrailingWhitespaceStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/generic/TrimTrailingWhitespaceStepTest.java @@ -19,7 +19,7 @@ import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.ResourceHarness; -import com.diffplug.spotless.StepEqualityTester; +import com.diffplug.spotless.SerializableEqualityTester; import com.diffplug.spotless.StepHarness; public class TrimTrailingWhitespaceStepTest extends ResourceHarness { @@ -51,10 +51,9 @@ public void trimTrailingWhitespace() throws Exception { @Test public void equality() throws Exception { - new StepEqualityTester() { + new SerializableEqualityTester() { @Override protected void setupTest(API api) { - api.assertThisEqualToThis(); api.areDifferentThan(); } diff --git a/testlib/src/test/java/com/diffplug/spotless/java/GoogleJavaFormatStepTest.java b/testlib/src/test/java/com/diffplug/spotless/java/GoogleJavaFormatStepTest.java index 7e4f25ce95..a42dad1af9 100644 --- a/testlib/src/test/java/com/diffplug/spotless/java/GoogleJavaFormatStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/java/GoogleJavaFormatStepTest.java @@ -21,7 +21,7 @@ import com.diffplug.common.base.StringPrinter; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.ResourceHarness; -import com.diffplug.spotless.StepEqualityTester; +import com.diffplug.spotless.SerializableEqualityTester; import com.diffplug.spotless.StepHarness; import com.diffplug.spotless.TestProvisioner; @@ -38,17 +38,15 @@ public void behavior() throws Exception { @Test public void equality() throws Exception { - new StepEqualityTester() { + new SerializableEqualityTester() { String version = "1.2"; @Override protected void setupTest(API api) { // same version == same - api.assertThisEqualToThis(); api.areDifferentThan(); // change the version, and it's different version = "1.1"; - api.assertThisEqualToThis(); api.areDifferentThan(); } diff --git a/testlib/src/test/java/com/diffplug/spotless/java/ImportOrderStepTest.java b/testlib/src/test/java/com/diffplug/spotless/java/ImportOrderStepTest.java index 5ebb8ec1d4..dc174d689f 100644 --- a/testlib/src/test/java/com/diffplug/spotless/java/ImportOrderStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/java/ImportOrderStepTest.java @@ -23,7 +23,7 @@ import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.NonSerializableList; import com.diffplug.spotless.ResourceHarness; -import com.diffplug.spotless.StepEqualityTester; +import com.diffplug.spotless.SerializableEqualityTester; public class ImportOrderStepTest extends ResourceHarness { @Test @@ -57,25 +57,21 @@ public void doesntThrowIfImportOrderIsntSerializable() { @Test public void equality() throws Exception { - new StepEqualityTester() { + new SerializableEqualityTester() { ImmutableList imports = ImmutableList.of(); @Override protected void setupTest(API api) { // same version == same - api.assertThisEqualToThis(); api.areDifferentThan(); // change the version, and it's different imports = ImmutableList.of("a"); - api.assertThisEqualToThis(); api.areDifferentThan(); // change the version, and it's different imports = ImmutableList.of("b"); - api.assertThisEqualToThis(); api.areDifferentThan(); // change the version, and it's different imports = ImmutableList.of("a", "b"); - api.assertThisEqualToThis(); api.areDifferentThan(); } diff --git a/testlib/src/test/java/com/diffplug/spotless/java/RemoveUnusedImportsStepTest.java b/testlib/src/test/java/com/diffplug/spotless/java/RemoveUnusedImportsStepTest.java index 2bba77cc37..db068e318e 100644 --- a/testlib/src/test/java/com/diffplug/spotless/java/RemoveUnusedImportsStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/java/RemoveUnusedImportsStepTest.java @@ -18,7 +18,7 @@ import org.junit.Test; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.StepEqualityTester; +import com.diffplug.spotless.SerializableEqualityTester; import com.diffplug.spotless.StepHarness; import com.diffplug.spotless.TestProvisioner; @@ -35,10 +35,9 @@ public void behavior() throws Exception { @Test public void equality() throws Exception { - new StepEqualityTester() { + new SerializableEqualityTester() { @Override protected void setupTest(API api) { - api.assertThisEqualToThis(); api.areDifferentThan(); } 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 497e6bf456..951d8838dd 100644 --- a/testlib/src/test/java/com/diffplug/spotless/kotlin/KtLintStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/kotlin/KtLintStepTest.java @@ -19,7 +19,7 @@ import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.ResourceHarness; -import com.diffplug.spotless.StepEqualityTester; +import com.diffplug.spotless.SerializableEqualityTester; import com.diffplug.spotless.StepHarness; import com.diffplug.spotless.TestProvisioner; @@ -37,17 +37,15 @@ public void behavior() throws Exception { @Test public void equality() throws Exception { - new StepEqualityTester() { + new SerializableEqualityTester() { String version = "0.2.2"; @Override protected void setupTest(API api) { // same version == same - api.assertThisEqualToThis(); api.areDifferentThan(); // change the version, and it's different version = "0.2.1"; - api.assertThisEqualToThis(); api.areDifferentThan(); } diff --git a/testlib/src/test/java/com/diffplug/spotless/markdown/FreshMarkStepTest.java b/testlib/src/test/java/com/diffplug/spotless/markdown/FreshMarkStepTest.java index 99ff9667c7..6f8a804a8a 100644 --- a/testlib/src/test/java/com/diffplug/spotless/markdown/FreshMarkStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/markdown/FreshMarkStepTest.java @@ -21,7 +21,7 @@ import org.junit.Test; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.StepEqualityTester; +import com.diffplug.spotless.SerializableEqualityTester; import com.diffplug.spotless.StepHarness; import com.diffplug.spotless.TestProvisioner; @@ -37,22 +37,19 @@ public void behavior() throws Exception { @Test public void equality() throws Exception { - new StepEqualityTester() { + new SerializableEqualityTester() { String version = "1.3.1"; Map props = new HashMap<>(); @Override protected void setupTest(API api) { // same version and props == same - api.assertThisEqualToThis(); api.areDifferentThan(); // change the version, and it's different version = "1.3.0"; - api.assertThisEqualToThis(); api.areDifferentThan(); // change the props, and it's different props.put("1", "2"); - api.assertThisEqualToThis(); api.areDifferentThan(); } 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 b08a53c0d7..21b4372f70 100644 --- a/testlib/src/test/java/com/diffplug/spotless/scala/ScalaFmtStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/scala/ScalaFmtStepTest.java @@ -22,7 +22,7 @@ import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.ResourceHarness; -import com.diffplug.spotless.StepEqualityTester; +import com.diffplug.spotless.SerializableEqualityTester; import com.diffplug.spotless.StepHarness; import com.diffplug.spotless.TestProvisioner; @@ -43,26 +43,22 @@ public void behaviorCustomConfig() throws Exception { @Test public void equality() throws Exception { - new StepEqualityTester() { + new SerializableEqualityTester() { String version = "0.5.1"; File configFile = null; @Override protected void setupTest(API api) throws IOException { // same version == same - api.assertThisEqualToThis(); api.areDifferentThan(); // change the version, and it's different version = "0.5.0"; - api.assertThisEqualToThis(); api.areDifferentThan(); // add a config file, and its different configFile = createTestFile("scala/scalafmt/scalafmt.conf"); - api.assertThisEqualToThis(); api.areDifferentThan(); // change the config file and its different configFile = createTestFile("scala/scalafmt/scalafmt2.conf"); - api.assertThisEqualToThis(); api.areDifferentThan(); }