diff --git a/CHANGES.md b/CHANGES.md index 5f1a1ffa9f..efb88d972d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,19 @@ 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`). ## [Unreleased] +### Fixed +* `JacksonJsonFormatterFunc` handles json files with an Array as root. ([#1585](https://github.com/diffplug/spotless/pull/1585)) + +## [2.35.0] - 2023-02-10 +### Added +* CleanThat Java Refactorer. ([#1560](https://github.com/diffplug/spotless/pull/1560)) +* Introduce `LazyArgLogger` to allow for lazy evaluation of log messages in slf4j logging. ([#1565](https://github.com/diffplug/spotless/pull/1565)) +### Fixed +* Allow multiple instances of the same npm-based formatter to be used by separating their `node_modules` directories. ([#1565](https://github.com/diffplug/spotless/pull/1565)) +* `ktfmt` default style uses correct continuation indent. ([#1562](https://github.com/diffplug/spotless/pull/1562)) +### Changes +* Bump default `ktfmt` version to latest `0.42` -> `0.43` ([#1561](https://github.com/diffplug/spotless/pull/1561)) +* Bump default `jackson` version to latest `2.14.1` -> `2.14.2` ([#1536](https://github.com/diffplug/spotless/pull/1536)) ## [2.34.1] - 2023-02-05 ### Changes diff --git a/README.md b/README.md index d2527034ca..662c624bdf 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ lib('java.PalantirJavaFormatStep') +'{{yes}} | {{yes}} lib('java.RemoveUnusedImportsStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', extra('java.EclipseJdtFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', lib('java.FormatAnnotationsStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', +lib('java.CleanthatJavaStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('json.gson.GsonStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('json.JacksonJsonStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('json.JsonSimpleStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', @@ -131,6 +132,7 @@ lib('yaml.JacksonYamlStep') +'{{yes}} | {{yes}} | [`java.RemoveUnusedImportsStep`](lib/src/main/java/com/diffplug/spotless/java/RemoveUnusedImportsStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`java.EclipseJdtFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`java.FormatAnnotationsStep`](lib/src/main/java/com/diffplug/spotless/java/FormatAnnotationsStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | +| [`java.CleanthatJavaStep`](lib/src/main/java/com/diffplug/spotless/java/CleanthatJavaStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`json.gson.GsonStep`](lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`json.JacksonJsonStep`](lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`json.JsonSimpleStep`](lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | diff --git a/gradle/java-publish.gradle b/gradle/java-publish.gradle index 7583bbe344..2eb877c429 100644 --- a/gradle/java-publish.gradle +++ b/gradle/java-publish.gradle @@ -170,11 +170,11 @@ model { } } -if (System.env['JITPACK'] == 'true') { +if (System.env['JITPACK'] == 'true' || version.endsWith('-SNAPSHOT')) { signing { setRequired(false) } -} else if (!version.endsWith('-SNAPSHOT')) { +} else { signing { String gpg_key = decode64('ORG_GRADLE_PROJECT_gpg_key64') useInMemoryPgpKeys(gpg_key, System.env['ORG_GRADLE_PROJECT_gpg_passphrase']) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f398c33c4b..508322917b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/GitAttributesLineEndings.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/GitAttributesLineEndings.java index c4ca8edacb..fb54be4ba0 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/GitAttributesLineEndings.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/GitAttributesLineEndings.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. @@ -42,6 +42,8 @@ import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.googlecode.concurrenttrees.radix.ConcurrentRadixTree; import com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharSequenceNodeFactory; @@ -61,6 +63,8 @@ * back to the platform native. */ public final class GitAttributesLineEndings { + private static final Logger LOGGER = LoggerFactory.getLogger(GitAttributesLineEndings.class); + // prevent direct instantiation private GitAttributesLineEndings() {} @@ -261,7 +265,7 @@ private static String convertEolToLineEnding(String eol, File file) { case "crlf": return LineEnding.WINDOWS.str(); default: - System.err.println(".gitattributes file has unspecified eol value: " + eol + " for " + file + ", defaulting to platform native"); + LOGGER.warn(".gitattributes file has unspecified eol value: {} for {}, defaulting to platform native", eol, file); return LineEnding.PLATFORM_NATIVE.str(); } } @@ -341,8 +345,7 @@ private static List parseRules(@Nullable File file) { return parsed.getRules(); } catch (IOException e) { // no need to crash the whole plugin - System.err.println("Problem parsing " + file.getAbsolutePath()); - e.printStackTrace(); + LOGGER.warn("Problem parsing {}", file.getAbsolutePath(), e); } } return Collections.emptyList(); diff --git a/lib/build.gradle b/lib/build.gradle index 9d95007bc1..f6e0446f73 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -16,7 +16,8 @@ def NEEDS_GLUE = [ 'diktat', 'scalafmt', 'jackson', - 'gson' + 'gson', + 'cleanthat' ] for (glue in NEEDS_GLUE) { sourceSets.register(glue) { @@ -39,6 +40,12 @@ versionCompatibility { ] targetSourceSetName = 'ktlint' } + namespaces.register('Cleanthat') { + versions = [ + '2.1', + ] + targetSourceSetName = 'cleanthat' + } } } @@ -62,10 +69,10 @@ dependencies { palantirJavaFormatCompileOnly 'com.palantir.javaformat:palantir-java-format:1.1.0' // this version needs to stay compilable against Java 8 for CI Job testNpm // used jackson-based formatters - jacksonCompileOnly 'com.fasterxml.jackson.core:jackson-databind:2.14.1' - jacksonCompileOnly 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.1' + jacksonCompileOnly 'com.fasterxml.jackson.core:jackson-databind:2.14.2' + jacksonCompileOnly 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.2' - String VER_KTFMT = '0.42' + String VER_KTFMT = '0.43' ktfmtCompileOnly "com.facebook:ktfmt:$VER_KTFMT" String VER_KTLINT_GOOGLE_JAVA_FORMAT = '1.7' // for JDK 8 compatibility ktfmtCompileOnly("com.google.googlejavaformat:google-java-format") { @@ -100,6 +107,9 @@ dependencies { flexmarkCompileOnly 'com.vladsch.flexmark:flexmark-all:0.62.2' gsonCompileOnly 'com.google.code.gson:gson:2.10.1' + + cleanthatCompileOnly 'io.github.solven-eu.cleanthat:java:2.6' + compatCleanthat2Dot1CompileAndTestOnly 'io.github.solven-eu.cleanthat:java:2.6' } // we'll hold the core lib to a high standard diff --git a/lib/src/cleanthat/java/com/diffplug/spotless/glue/java/JavaCleanthatRefactorerFunc.java b/lib/src/cleanthat/java/com/diffplug/spotless/glue/java/JavaCleanthatRefactorerFunc.java new file mode 100644 index 0000000000..d7148f8892 --- /dev/null +++ b/lib/src/cleanthat/java/com/diffplug/spotless/glue/java/JavaCleanthatRefactorerFunc.java @@ -0,0 +1,95 @@ +/* + * Copyright 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.glue.java; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.FormatterFunc; + +import eu.solven.cleanthat.config.pojo.CleanthatEngineProperties; +import eu.solven.cleanthat.config.pojo.SourceCodeProperties; +import eu.solven.cleanthat.engine.java.IJdkVersionConstants; +import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorer; +import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorerProperties; +import eu.solven.cleanthat.formatter.LineEnding; + +/** + * The glue for CleanThat: it is build over the version in build.gradle, but at runtime it will be executed over + * the version loaded in JarState, which is by default defined in com.diffplug.spotless.java.CleanthatJavaStep#JVM_SUPPORT + */ +public class JavaCleanthatRefactorerFunc implements FormatterFunc { + private static final Logger LOGGER = LoggerFactory.getLogger(JavaCleanthatRefactorerFunc.class); + + private String jdkVersion; + private List included; + private List excluded; + private boolean includeDraft; + + public JavaCleanthatRefactorerFunc(String jdkVersion, List included, List excluded, boolean includeDraft) { + this.jdkVersion = jdkVersion == null ? IJdkVersionConstants.JDK_8 : jdkVersion; + this.included = included == null ? Collections.emptyList() : included; + this.excluded = excluded == null ? Collections.emptyList() : excluded; + this.includeDraft = includeDraft; + } + + public JavaCleanthatRefactorerFunc() { + this(IJdkVersionConstants.JDK_8, Arrays.asList("SafeAndConsensual"), Arrays.asList(), false); + } + + @Override + public String apply(String input) throws Exception { + // https://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); + try { + // Ensure CleanThat main Thread has its custom classLoader while executing its refactoring + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + return doApply(input); + } finally { + // Restore the originalClassLoader + Thread.currentThread().setContextClassLoader(originalClassLoader); + } + } + + private String doApply(String input) throws InterruptedException, IOException { + // call some API that uses reflection without taking ClassLoader param + CleanthatEngineProperties engineProperties = CleanthatEngineProperties.builder().engineVersion(jdkVersion).build(); + + // Spotless will push us LF content + engineProperties.setSourceCode(SourceCodeProperties.builder().lineEnding(LineEnding.LF).build()); + + JavaRefactorerProperties refactorerProperties = new JavaRefactorerProperties(); + + refactorerProperties.setIncluded(included); + refactorerProperties.setExcluded(excluded); + + refactorerProperties.setIncludeDraft(includeDraft); + + JavaRefactorer refactorer = new JavaRefactorer(engineProperties, refactorerProperties); + + LOGGER.debug("Processing sourceJdk={} included={} excluded={}", jdkVersion, included, excluded, includeDraft); + LOGGER.debug("Available mutators: {}", JavaRefactorer.getAllIncluded()); + + // Spotless calls steps always with LF eol. + return refactorer.doFormat(input, LineEnding.LF); + } + +} diff --git a/lib/src/jackson/java/com/diffplug/spotless/glue/json/AJacksonFormatterFunc.java b/lib/src/jackson/java/com/diffplug/spotless/glue/json/AJacksonFormatterFunc.java index 6f363ad1b7..1f317fa3b4 100644 --- a/lib/src/jackson/java/com/diffplug/spotless/glue/json/AJacksonFormatterFunc.java +++ b/lib/src/jackson/java/com/diffplug/spotless/glue/json/AJacksonFormatterFunc.java @@ -16,7 +16,6 @@ package com.diffplug.spotless.glue.json; import java.io.IOException; -import java.util.Map; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonProcessingException; @@ -49,7 +48,7 @@ public String apply(String input) throws Exception { protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { try { // ObjectNode is not compatible with SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS - Map objectNode = objectMapper.readValue(input, Map.class); + Object objectNode = objectMapper.readValue(input, inferType(input)); String output = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode); return output; @@ -58,6 +57,13 @@ protected String format(ObjectMapper objectMapper, String input) throws IllegalA } } + /** + * + * @param input + * @return the {@link Class} into which the String has to be deserialized + */ + protected abstract Class inferType(String input); + /** * @return a {@link JsonFactory}. May be overridden to handle alternative formats. * @see jackson-dataformats-text diff --git a/lib/src/jackson/java/com/diffplug/spotless/glue/json/JacksonJsonFormatterFunc.java b/lib/src/jackson/java/com/diffplug/spotless/glue/json/JacksonJsonFormatterFunc.java index ae455fbbb4..71215eb4a3 100644 --- a/lib/src/jackson/java/com/diffplug/spotless/glue/json/JacksonJsonFormatterFunc.java +++ b/lib/src/jackson/java/com/diffplug/spotless/glue/json/JacksonJsonFormatterFunc.java @@ -15,6 +15,9 @@ */ package com.diffplug.spotless.glue.json; +import java.util.Collection; +import java.util.Map; + import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonFactoryBuilder; import com.fasterxml.jackson.core.JsonGenerator; @@ -37,6 +40,15 @@ public JacksonJsonFormatterFunc(JacksonJsonConfig jacksonConfig) { this.jacksonConfig = jacksonConfig; } + @Override + protected Class inferType(String input) { + if (input.trim().startsWith("[")) { + return Collection.class; + } else { + return Map.class; + } + } + /** * @return a {@link JsonFactory}. May be overridden to handle alternative formats. * @see jackson-dataformats-text diff --git a/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/JacksonYamlFormatterFunc.java b/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/JacksonYamlFormatterFunc.java index 89304d8d0a..b4f8ca6aee 100644 --- a/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/JacksonYamlFormatterFunc.java +++ b/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/JacksonYamlFormatterFunc.java @@ -58,13 +58,18 @@ protected JsonFactory makeJsonFactory() { return yamlFactoryBuilder.build(); } + @Override + protected Class inferType(String input) { + return JsonNode.class; + } + @Override protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { try { // https://stackoverflow.com/questions/25222327/deserialize-pojos-from-multiple-yaml-documents-in-a-single-file-in-jackson // https://github.com/FasterXML/jackson-dataformats-text/issues/66#issuecomment-375328648 JsonParser yamlParser = objectMapper.getFactory().createParser(input); - List documents = objectMapper.readValues(yamlParser, JsonNode.class).readAll(); + List documents = objectMapper.readValues(yamlParser, inferType(input)).readAll(); // https://github.com/FasterXML/jackson-dataformats-text/issues/66#issuecomment-554265055 // https://github.com/FasterXML/jackson-dataformats-text/issues/66#issuecomment-554265055 diff --git a/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtFormatterFunc.java b/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtFormatterFunc.java index 72e0e50e3c..80f4a42fcd 100644 --- a/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtFormatterFunc.java +++ b/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtFormatterFunc.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. @@ -78,7 +78,7 @@ private FormattingOptions createFormattingOptions() { formattingOptions.getStyle(), ktfmtFormattingOptions.getMaxWidth().orElse(formattingOptions.getMaxWidth()), ktfmtFormattingOptions.getBlockIndent().orElse(formattingOptions.getBlockIndent()), - ktfmtFormattingOptions.getContinuationIndent().orElse(formattingOptions.getBlockIndent()), + ktfmtFormattingOptions.getContinuationIndent().orElse(formattingOptions.getContinuationIndent()), ktfmtFormattingOptions.getRemoveUnusedImport().orElse(formattingOptions.getRemoveUnusedImports()), formattingOptions.getDebuggingPrintOpsAfterFormatting()); } diff --git a/lib/src/main/java/com/diffplug/spotless/LazyArgLogger.java b/lib/src/main/java/com/diffplug/spotless/LazyArgLogger.java new file mode 100644 index 0000000000..e3456a2317 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/LazyArgLogger.java @@ -0,0 +1,40 @@ +/* + * Copyright 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.util.function.Supplier; + +/** + * This is a utility class to allow for lazy evaluation of arguments to be passed to a logger + * and thus avoid unnecessary computation of the arguments if the log level is not enabled. + */ +public final class LazyArgLogger { + + private final Supplier argSupplier; + + private LazyArgLogger(Supplier argSupplier) { + this.argSupplier = argSupplier; + } + + public static LazyArgLogger lazy(Supplier argSupplier) { + return new LazyArgLogger(argSupplier); + } + + @Override + public String toString() { + return String.valueOf(argSupplier.get()); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/SpotlessCache.java b/lib/src/main/java/com/diffplug/spotless/SpotlessCache.java index b90682f1b6..237e326482 100644 --- a/lib/src/main/java/com/diffplug/spotless/SpotlessCache.java +++ b/lib/src/main/java/com/diffplug/spotless/SpotlessCache.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. @@ -27,6 +27,9 @@ import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** @@ -34,6 +37,8 @@ * when Spotless is no longer in use to release any resources it has grabbed. */ public final class SpotlessCache { + private static final Logger LOGGER = LoggerFactory.getLogger(SpotlessCache.class); + /** Allows comparing keys based on their serialization. */ static final class SerializedKey { final byte[] serialized; @@ -68,7 +73,10 @@ synchronized ClassLoader classloader(JarState state) { synchronized ClassLoader classloader(Serializable key, JarState state) { SerializedKey serializedKey = new SerializedKey(key); return cache - .computeIfAbsent(serializedKey, k -> new FeatureClassLoader(state.jarUrls(), this.getClass().getClassLoader())); + .computeIfAbsent(serializedKey, k -> { + LOGGER.debug("Allocating an additional FeatureClassLoader for key={} Cache.size was {}", key, cache.size()); + return new FeatureClassLoader(state.jarUrls(), this.getClass().getClassLoader()); + }); } static SpotlessCache instance() { diff --git a/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java b/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java index 46312ca07a..b4c0cf2a6f 100644 --- a/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java +++ b/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java @@ -31,6 +31,9 @@ import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.diffplug.spotless.FileSignature; import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.FormatterStep; @@ -42,6 +45,8 @@ /** Prefixes a license header before the package statement. */ public final class LicenseHeaderStep { + private static final Logger LOGGER = LoggerFactory.getLogger(LicenseHeaderStep.class); + public enum YearMode { PRESERVE, UPDATE_TO_TODAY, SET_FROM_GIT } @@ -381,7 +386,7 @@ private String calculateYearBySearching(String content) { } } } else { - System.err.println("Can't parse copyright year '" + content + "', defaulting to " + yearToday); + LOGGER.warn("Can't parse copyright year '{}', defaulting to {}", content, yearToday); // couldn't recognize the year format return yearToday; } diff --git a/lib/src/main/java/com/diffplug/spotless/java/CleanthatJavaStep.java b/lib/src/main/java/com/diffplug/spotless/java/CleanthatJavaStep.java new file mode 100644 index 0000000000..c1e5ae1fa4 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/java/CleanthatJavaStep.java @@ -0,0 +1,178 @@ +/* + * Copyright 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.java; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Objects; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.JarState; +import com.diffplug.spotless.Jvm; +import com.diffplug.spotless.Provisioner; + +/** + * Enables CleanThat as a SpotLess step. + * + * @author Benoit Lacelle + */ +// https://github.com/diffplug/spotless/blob/main/CONTRIBUTING.md#how-to-add-a-new-formatterstep +public final class CleanthatJavaStep { + + private static final String NAME = "cleanthat"; + private static final String MAVEN_COORDINATE = "io.github.solven-eu.cleanthat:java"; + + // CleanThat changelog is available at https://github.com/solven-eu/cleanthat/blob/master/CHANGES.MD + private static final Jvm.Support JVM_SUPPORT = Jvm. support(NAME).add(11, "2.6"); + + // prevent direct instantiation + private CleanthatJavaStep() {} + + /** Creates a step which apply default CleanThat mutators. */ + public static FormatterStep create(Provisioner provisioner) { + return create(defaultVersion(), provisioner); + } + + /** Creates a step which apply default CleanThat mutators. */ + public static FormatterStep create(String version, Provisioner provisioner) { + return create(MAVEN_COORDINATE, version, defaultSourceJdk(), defaultMutators(), defaultExcludedMutators(), defaultIncludeDraft(), provisioner); + } + + public static String defaultSourceJdk() { + // see IJdkVersionConstants.JDK_7 + // https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#source + // 1.7 is the default for 'maven-compiler-plugin' since 3.9.0 + return "1.7"; + } + + /** + * By default, we include only safe and consensual mutators + * @return + */ + public static List defaultMutators() { + // see ICleanthatStepParametersProperties.SAFE_AND_CONSENSUAL + return List.of("SafeAndConsensual"); + } + + public static List defaultExcludedMutators() { + return List.of(); + } + + public static boolean defaultIncludeDraft() { + return false; + } + + /** Creates a step which apply selected CleanThat mutators. */ + public static FormatterStep create(String groupArtifact, + String version, + String sourceJdkVersion, + List excluded, + List included, + boolean includeDraft, + Provisioner provisioner) { + Objects.requireNonNull(groupArtifact, "groupArtifact"); + if (groupArtifact.chars().filter(ch -> ch == ':').count() != 1) { + throw new IllegalArgumentException("groupArtifact must be in the form 'groupId:artifactId'. it was: " + groupArtifact); + } + Objects.requireNonNull(version, "version"); + Objects.requireNonNull(provisioner, "provisioner"); + return FormatterStep.createLazy(NAME, + () -> new JavaRefactorerState(NAME, groupArtifact, version, sourceJdkVersion, excluded, included, includeDraft, provisioner), + JavaRefactorerState::createFormat); + } + + /** Get default formatter version */ + public static String defaultVersion() { + return JVM_SUPPORT.getRecommendedFormatterVersion(); + } + + public static String defaultGroupArtifact() { + return MAVEN_COORDINATE; + } + + static final class JavaRefactorerState implements Serializable { + private static final long serialVersionUID = 1L; + + final JarState jarState; + final String stepName; + final String version; + + final String sourceJdkVersion; + final List included; + final List excluded; + final boolean includeDraft; + + JavaRefactorerState(String stepName, String version, Provisioner provisioner) throws IOException { + this(stepName, MAVEN_COORDINATE, version, defaultSourceJdk(), defaultExcludedMutators(), defaultMutators(), defaultIncludeDraft(), provisioner); + } + + JavaRefactorerState(String stepName, + String groupArtifact, + String version, + String sourceJdkVersion, + List included, + List excluded, + boolean includeDraft, + Provisioner provisioner) throws IOException { + // https://github.com/diffplug/spotless/issues/1583 + if (!version.endsWith("-SNAPSHOT")) { + JVM_SUPPORT.assertFormatterSupported(version); + } + ModuleHelper.doOpenInternalPackagesIfRequired(); + this.jarState = JarState.from(groupArtifact + ":" + version, provisioner); + this.stepName = stepName; + this.version = version; + + this.sourceJdkVersion = sourceJdkVersion; + this.included = included; + this.excluded = excluded; + this.includeDraft = includeDraft; + } + + @SuppressWarnings("PMD.UseProperClassLoader") + FormatterFunc createFormat() { + ClassLoader classLoader = jarState.getClassLoader(); + + Object formatter; + Method formatterMethod; + try { + Class formatterClazz = classLoader.loadClass("com.diffplug.spotless.glue.java.JavaCleanthatRefactorerFunc"); + Constructor formatterConstructor = formatterClazz.getConstructor(String.class, List.class, List.class, boolean.class); + + formatter = formatterConstructor.newInstance(sourceJdkVersion, included, excluded, includeDraft); + formatterMethod = formatterClazz.getMethod("apply", String.class); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Issue executing the formatter", e); + } + + // https://github.com/diffplug/spotless/issues/1583 + if (!version.endsWith("-SNAPSHOT")) { + return JVM_SUPPORT.suggestLaterVersionOnError(version, input -> { + return (String) formatterMethod.invoke(formatter, input); + }); + } else { + return input -> { + return (String) formatterMethod.invoke(formatter, input); + }; + } + } + + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/java/ModuleHelper.java b/lib/src/main/java/com/diffplug/spotless/java/ModuleHelper.java index 415c58d69b..e05a6a8b7f 100644 --- a/lib/src/main/java/com/diffplug/spotless/java/ModuleHelper.java +++ b/lib/src/main/java/com/diffplug/spotless/java/ModuleHelper.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. @@ -28,12 +28,17 @@ import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.diffplug.spotless.Jvm; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import sun.misc.Unsafe; final class ModuleHelper { + private static final Logger LOGGER = LoggerFactory.getLogger(ModuleHelper.class); + // prevent direct instantiation private ModuleHelper() {} @@ -66,11 +71,11 @@ public static synchronized void doOpenInternalPackagesIfRequired() { for (String name : failedToOpen) { message.append(String.format("--add-opens jdk.compiler/%s=ALL-UNNAMED", name)); } - System.err.println(message); + LOGGER.warn("{}", message); } } } catch (Throwable e) { - System.err.println("WARNING: Failed to check for unavailable JDK packages. Reason: " + e.getMessage()); + LOGGER.error("WARNING: Failed to check for available JDK packages.", e); } } diff --git a/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java b/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java index f15edbbd42..94c48e0c5c 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java @@ -33,7 +33,7 @@ public class JacksonJsonStep { static final String MAVEN_COORDINATE = "com.fasterxml.jackson.core:jackson-databind:"; // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind - static final String DEFAULT_VERSION = "2.14.1"; + static final String DEFAULT_VERSION = "2.14.2"; private JacksonJsonStep() {} diff --git a/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java b/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java index b6f7a533eb..d6a20bc5b3 100644 --- a/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java +++ b/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java @@ -39,7 +39,7 @@ public class KtfmtStep { // prevent direct instantiation private KtfmtStep() {} - private static final String DEFAULT_VERSION = "0.42"; + private static final String DEFAULT_VERSION = "0.43"; static final String NAME = "ktfmt"; static final String PACKAGE = "com.facebook"; static final String MAVEN_COORDINATE = PACKAGE + ":ktfmt:"; diff --git a/lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java index 221a745ad7..b262bb4b98 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java @@ -15,6 +15,7 @@ */ package com.diffplug.spotless.npm; +import static com.diffplug.spotless.LazyArgLogger.lazy; import static java.util.Objects.requireNonNull; import java.io.File; @@ -115,7 +116,7 @@ protected void prepareNodeServerLayout() throws IOException { // If any config files are provided, we need to make sure they are at the same location as the node modules // as eslint will try to resolve plugin/config names relatively to the config file location and some // eslint configs contain relative paths to additional config files (such as tsconfig.json e.g.) - FormattedPrinter.SYSOUT.print("Copying config file <%s> to <%s> and using the copy", origEslintConfig.getEslintConfigPath(), nodeServerLayout.nodeModulesDir()); + logger.info("Copying config file <{}> to <{}> and using the copy", origEslintConfig.getEslintConfigPath(), nodeServerLayout.nodeModulesDir()); File configFileCopy = NpmResourceHelper.copyFileToDir(origEslintConfig.getEslintConfigPath(), nodeServerLayout.nodeModulesDir()); this.eslintConfigInUse = this.origEslintConfig.withEslintConfigPath(configFileCopy).verify(); } @@ -125,7 +126,7 @@ protected void prepareNodeServerLayout() throws IOException { @Nonnull public FormatterFunc createFormatterFunc() { try { - FormattedPrinter.SYSOUT.print("creating formatter function (starting server)"); + logger.info("Creating formatter function (starting server)"); ServerProcessInfo eslintRestServer = npmRunServer(); EslintRestService restService = new EslintRestService(eslintRestServer.getBaseUrl()); return Closeable.ofDangerous(() -> endServer(restService, eslintRestServer), new EslintFilePathPassingFormatterFunc(locations.projectDir(), nodeServerLayout.nodeModulesDir(), eslintConfigInUse, restService)); @@ -135,7 +136,7 @@ public FormatterFunc createFormatterFunc() { } private void endServer(BaseNpmRestService restService, ServerProcessInfo restServer) throws Exception { - FormattedPrinter.SYSOUT.print("Closing formatting function (ending server)."); + logger.info("Closing formatting function (ending server)."); try { restService.shutdown(); } catch (Throwable t) { @@ -161,7 +162,7 @@ public EslintFilePathPassingFormatterFunc(File projectDir, File nodeModulesDir, @Override public String applyWithFile(String unix, File file) throws Exception { - FormattedPrinter.SYSOUT.print("formatting String '" + unix.substring(0, Math.min(50, unix.length())) + "[...]' in file '" + file + "'"); + logger.info("formatting String '{}[...]' in file '{}'", lazy(() -> unix.substring(0, Math.min(50, unix.length()))), file); Map eslintCallOptions = new HashMap<>(); setConfigToCallOptions(eslintCallOptions); diff --git a/lib/src/main/java/com/diffplug/spotless/npm/FormattedPrinter.java b/lib/src/main/java/com/diffplug/spotless/npm/FormattedPrinter.java deleted file mode 100644 index 97d5cdb4c6..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/npm/FormattedPrinter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020 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.npm; - -import static java.util.Objects.requireNonNull; - -import java.io.PrintStream; -import java.time.LocalDateTime; - -enum FormattedPrinter { - SYSOUT(System.out); - - private static final boolean enabled = false; - - private final PrintStream printStream; - - FormattedPrinter(PrintStream printStream) { - this.printStream = requireNonNull(printStream); - } - - public void print(String msg, Object... paramsForStringFormat) { - if (!enabled) { - return; - } - String formatted = String.format(msg, paramsForStringFormat); - String prefixed = String.format("[%s] %s", LocalDateTime.now().toString(), formatted); - this.printStream.println(prefixed); - } -} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeServerLayout.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeServerLayout.java index 63a6a923da..8b39c5e4ab 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NodeServerLayout.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NodeServerLayout.java @@ -18,25 +18,44 @@ import java.io.File; import java.nio.file.Files; import java.nio.file.Path; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Stream; import com.diffplug.spotless.ThrowingEx; class NodeServerLayout { + private static final Pattern PACKAGE_JSON_NAME_PATTERN = Pattern.compile("\"name\"\\s*:\\s*\"([^\"]+)\""); + private final File nodeModulesDir; private final File packageJsonFile; + + private final File packageLockJsonFile; + private final File serveJsFile; private final File npmrcFile; - NodeServerLayout(File buildDir, String stepName) { - this.nodeModulesDir = new File(buildDir, "spotless-node-modules-" + stepName); + NodeServerLayout(File buildDir, String packageJsonContent) { + this.nodeModulesDir = new File(buildDir, nodeModulesDirName(packageJsonContent)); this.packageJsonFile = new File(nodeModulesDir, "package.json"); + this.packageLockJsonFile = new File(nodeModulesDir, "package-lock.json"); this.serveJsFile = new File(nodeModulesDir, "serve.js"); this.npmrcFile = new File(nodeModulesDir, ".npmrc"); } + private static String nodeModulesDirName(String packageJsonContent) { + String md5Hash = NpmResourceHelper.md5(packageJsonContent); + Matcher matcher = PACKAGE_JSON_NAME_PATTERN.matcher(packageJsonContent); + if (!matcher.find()) { + throw new IllegalArgumentException("package.json must contain a name property"); + } + String packageName = matcher.group(1); + return String.format("%s-node-modules-%s", packageName, md5Hash); + } + File nodeModulesDir() { + return nodeModulesDir; } @@ -52,10 +71,6 @@ public File npmrcFile() { return npmrcFile; } - static File getBuildDirFromNodeModulesDir(File nodeModulesDir) { - return nodeModulesDir.getParentFile(); - } - public boolean isLayoutPrepared() { if (!nodeModulesDir().isDirectory()) { return false; @@ -63,6 +78,9 @@ public boolean isLayoutPrepared() { if (!packageJsonFile().isFile()) { return false; } + if (!packageLockJsonFile.isFile()) { + return false; + } if (!serveJsFile().isFile()) { return false; } @@ -82,4 +100,14 @@ public boolean isNodeModulesPrepared() { } }); } + + @Override + public String toString() { + return String.format( + "NodeServerLayout[nodeModulesDir=%s, packageJsonFile=%s, serveJsFile=%s, npmrcFile=%s]", + this.nodeModulesDir, + this.packageJsonFile, + this.serveJsFile, + this.npmrcFile); + } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java index 92ac7df6f2..f3f8a80fe8 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -56,10 +56,13 @@ protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, NpmFor this.stepName = requireNonNull(stepName); this.npmConfig = requireNonNull(npmConfig); this.locations = locations; - this.nodeServerLayout = new NodeServerLayout(locations.buildDir(), stepName); + this.nodeServerLayout = new NodeServerLayout(locations.buildDir(), npmConfig.getPackageJsonContent()); } protected void prepareNodeServerLayout() throws IOException { + final long started = System.currentTimeMillis(); + // maybe introduce trace logger? + logger.info("Preparing {} for npm step {}.", this.nodeServerLayout, getClass().getName()); NpmResourceHelper.assertDirectoryExists(nodeServerLayout.nodeModulesDir()); NpmResourceHelper.writeUtf8StringToFile(nodeServerLayout.packageJsonFile(), this.npmConfig.getPackageJsonContent()); @@ -70,12 +73,14 @@ protected void prepareNodeServerLayout() throws IOException { } else { NpmResourceHelper.deleteFileIfExists(nodeServerLayout.npmrcFile()); } + logger.info("Prepared {} for npm step {} in {} ms.", this.nodeServerLayout, getClass().getName(), System.currentTimeMillis() - started); } protected void prepareNodeServer() throws IOException { - FormattedPrinter.SYSOUT.print("running npm install"); + final long started = System.currentTimeMillis(); + logger.info("running npm install in {} for npm step {}", this.nodeServerLayout.nodeModulesDir(), getClass().getName()); runNpmInstall(nodeServerLayout.nodeModulesDir()); - FormattedPrinter.SYSOUT.print("npm install finished"); + logger.info("npm install finished in {} ms in {} for npm step {}", System.currentTimeMillis() - started, this.nodeServerLayout.nodeModulesDir(), getClass().getName()); } private void runNpmInstall(File npmProjectDir) throws IOException { diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java index 2acf3a180d..aa66c54fcf 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java @@ -21,6 +21,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.security.MessageDigest; import java.time.Duration; import java.util.Arrays; import java.util.Objects; @@ -122,4 +123,20 @@ static File copyFileToDirAtSubpath(File file, File targetDir, String relativePat throw ThrowingEx.asRuntime(e); } } + + static String md5(File file) { + return md5(readUtf8StringFromFile(file)); + } + + static String md5(String fileContent) { + MessageDigest md = ThrowingEx.get(() -> MessageDigest.getInstance("MD5")); + md.update(fileContent.getBytes(StandardCharsets.UTF_8)); + byte[] digest = md.digest(); + // convert byte array digest to hex string + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b & 0xff)); + } + return sb.toString(); + } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java index 22f1a69e7c..05c61f9bdf 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -15,6 +15,7 @@ */ package com.diffplug.spotless.npm; +import static com.diffplug.spotless.LazyArgLogger.lazy; import static java.util.Objects.requireNonNull; import java.io.File; @@ -86,7 +87,7 @@ private static class State extends NpmFormatterStepStateBase implements Serializ @Nonnull public FormatterFunc createFormatterFunc() { try { - FormattedPrinter.SYSOUT.print("creating formatter function (starting server)"); + logger.info("creating formatter function (starting server)"); ServerProcessInfo prettierRestServer = npmRunServer(); PrettierRestService restService = new PrettierRestService(prettierRestServer.getBaseUrl()); String prettierConfigOptions = restService.resolveConfig(this.prettierConfig.getPrettierConfigPath(), this.prettierConfig.getOptions()); @@ -97,7 +98,7 @@ public FormatterFunc createFormatterFunc() { } private void endServer(PrettierRestService restService, ServerProcessInfo restServer) throws Exception { - FormattedPrinter.SYSOUT.print("Closing formatting function (ending server)."); + logger.info("Closing formatting function (ending server)."); try { restService.shutdown(); } catch (Throwable t) { @@ -119,7 +120,7 @@ public PrettierFilePathPassingFormatterFunc(String prettierConfigOptions, Pretti @Override public String applyWithFile(String unix, File file) throws Exception { - FormattedPrinter.SYSOUT.print("formatting String '" + unix.substring(0, Math.min(50, unix.length())) + "[...]' in file '" + file + "'"); + logger.info("formatting String '{}[...]' in file '{}'", lazy(() -> unix.substring(0, Math.min(50, unix.length()))), file); final String prettierConfigOptionsWithFilepath = assertFilepathInConfigOptions(file); try { diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/eslint-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/eslint-package.json index dcd91e729d..0d7ce930f7 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/eslint-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/eslint-package.json @@ -1,5 +1,5 @@ { - "name": "spotless-eslint-formatter-step", + "name": "spotless-eslint", "version": "2.0.0", "description": "Spotless formatter step for running eslint as a rest service.", "repository": "https://github.com/diffplug/spotless", diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json index 7bda08db8a..395a05da67 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json @@ -1,5 +1,5 @@ { - "name": "spotless-prettier-formatter-step", + "name": "spotless-prettier", "version": "2.0.0", "description": "Spotless formatter step for running prettier as a rest service.", "repository": "https://github.com/diffplug/spotless", diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json index 7037bd2ec1..483dd0753d 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json @@ -1,5 +1,5 @@ { - "name": "spotless-tsfmt-formatter-step", + "name": "spotless-tsfmt", "version": "2.0.0", "description": "Spotless formatter step for running tsfmt as a rest service.", "repository": "https://github.com/diffplug/spotless", diff --git a/lib/src/testCompatCleanthat2Dot1/java/com/diffplug/spotless/glue/java/JavaCleanthatRefactorerFuncTest.java b/lib/src/testCompatCleanthat2Dot1/java/com/diffplug/spotless/glue/java/JavaCleanthatRefactorerFuncTest.java new file mode 100644 index 0000000000..2f3cdc9646 --- /dev/null +++ b/lib/src/testCompatCleanthat2Dot1/java/com/diffplug/spotless/glue/java/JavaCleanthatRefactorerFuncTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 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.glue.java; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorer; + +public class JavaCleanthatRefactorerFuncTest { + @Test + public void testMutatorsDetection() { + Assertions.assertThat(JavaRefactorer.getAllIncluded()).isNotEmpty(); + } +} diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index e3234584b8..84b02012b8 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -3,6 +3,23 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`). ## [Unreleased] +### Added +* `cleanthat` now has `includeDraft` option, to include draft mutators from composite mutators ([#1574](https://github.com/diffplug/spotless/pull/1574)) +### Fixed +* `json { jackson()` can now handle `Array` as a root element. ([#1585](https://github.com/diffplug/spotless/pull/1585)) +### Changes +* Bump default `cleanthat` version to latest `2.1` -> `2.6` ([#1569](https://github.com/diffplug/spotless/pull/1569) and [#1574](https://github.com/diffplug/spotless/pull/1574)) + +## [6.15.0] - 2023-02-10 +### Added +* CleanThat Java Refactorer. ([#1560](https://github.com/diffplug/spotless/pull/1560)) +### Fixed +* Allow multiple instances of the same npm-based formatter to be used simultaneously. E.g. use prettier for typescript + *and* Java (using the community prettier-plugin-java) without messing up their respective `node_module` dependencies. ([#1565](https://github.com/diffplug/spotless/pull/1565)) +* `ktfmt` default style uses correct continuation indent. ([#1562](https://github.com/diffplug/spotless/pull/1562)) +### Changes +* Bump default `ktfmt` version to latest `0.42` -> `0.43` ([#1561](https://github.com/diffplug/spotless/pull/1561)) +* Bump default `jackson` version to latest `2.14.1` -> `2.14.2` ([#1536](https://github.com/diffplug/spotless/pull/1536)) ## [6.14.1] - 2023-02-05 ### Fixed diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index f61280bc7e..106cf3dcfd 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -14,9 +14,9 @@ output = [ ].join('\n'); --> [![Gradle plugin](https://img.shields.io/badge/plugins.gradle.org-com.diffplug.spotless-blue.svg)](https://plugins.gradle.org/plugin/com.diffplug.spotless) -[![Changelog](https://img.shields.io/badge/changelog-6.14.1-blue.svg)](CHANGES.md) +[![Changelog](https://img.shields.io/badge/changelog-6.15.0-blue.svg)](CHANGES.md) [![Maven central](https://img.shields.io/badge/mavencentral-here-blue.svg)](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.diffplug.spotless%22%20AND%20a%3A%22spotless-plugin-gradle%22) -[![Javadoc](https://img.shields.io/badge/javadoc-here-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/index.html) +[![Javadoc](https://img.shields.io/badge/javadoc-here-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/index.html) [![VS Code plugin](https://img.shields.io/badge/IDE-VS_Code-blueviolet.svg)](https://marketplace.visualstudio.com/items?itemName=richardwillis.vscode-spotless-gradle) [![IntelliJ plugin](https://img.shields.io/badge/IDE-IntelliJ-blueviolet.svg)](https://plugins.jetbrains.com/plugin/18321-spotless-gradle) @@ -54,7 +54,7 @@ Spotless supports all of Gradle's built-in performance features (incremental bui - [**Quickstart**](#quickstart) - [Requirements](#requirements) - **Languages** - - [Java](#java) ([google-java-format](#google-java-format), [eclipse jdt](#eclipse-jdt), [clang-format](#clang-format), [prettier](#prettier), [palantir-java-format](#palantir-java-format), [formatAnnotations](#formatAnnotations)) + - [Java](#java) ([google-java-format](#google-java-format), [eclipse jdt](#eclipse-jdt), [clang-format](#clang-format), [prettier](#prettier), [palantir-java-format](#palantir-java-format), [formatAnnotations](#formatAnnotations), [cleanthat](#cleanthat)) - [Groovy](#groovy) ([eclipse groovy](#eclipse-groovy)) - [Kotlin](#kotlin) ([ktfmt](#ktfmt), [ktlint](#ktlint), [diktat](#diktat), [prettier](#prettier)) - [Scala](#scala) ([scalafmt](#scalafmt)) @@ -123,10 +123,10 @@ spotless { ``` Spotless consists of a list of formats (in the example above, `misc` and `java`), and each format has: -- a `target` (the files to format), which you set with [`target`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html#target-java.lang.Object...-) and [`targetExclude`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html#targetExclude-java.lang.Object...-) -- a list of `FormatterStep`, which are just `String -> String` functions, such as [`replace`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html#replace-java.lang.String-java.lang.CharSequence-java.lang.CharSequence-), [`replaceRegex`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html#replaceRegex-java.lang.String-java.lang.String-java.lang.String-), [`trimTrailingWhitespace`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html#replace-java.lang.String-java.lang.CharSequence-java.lang.CharSequence-), [`custom`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html#custom-java.lang.String-groovy.lang.Closure-), [`prettier`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html#prettier--), [`eclipseWtp`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html#eclipseWtp-com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep-), [`licenseHeader`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html#licenseHeader-java.lang.String-java.lang.String-) etc. +- a `target` (the files to format), which you set with [`target`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html#target-java.lang.Object...-) and [`targetExclude`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html#targetExclude-java.lang.Object...-) +- a list of `FormatterStep`, which are just `String -> String` functions, such as [`replace`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html#replace-java.lang.String-java.lang.CharSequence-java.lang.CharSequence-), [`replaceRegex`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html#replaceRegex-java.lang.String-java.lang.String-java.lang.String-), [`trimTrailingWhitespace`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html#replace-java.lang.String-java.lang.CharSequence-java.lang.CharSequence-), [`custom`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html#custom-java.lang.String-groovy.lang.Closure-), [`prettier`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html#prettier--), [`eclipseWtp`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html#eclipseWtp-com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep-), [`licenseHeader`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html#licenseHeader-java.lang.String-java.lang.String-) etc. -All the generic steps live in [`FormatExtension`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html), and there are many language-specific steps which live in its language-specific subclasses, which are described below. +All the generic steps live in [`FormatExtension`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html), and there are many language-specific steps which live in its language-specific subclasses, which are described below. ### Requirements @@ -138,7 +138,7 @@ If you're stuck on an older version of Gradle, `id 'com.diffplug.gradle.spotless ## Java -`com.diffplug.gradle.spotless.JavaExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/JavaExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java) +`com.diffplug.gradle.spotless.JavaExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/JavaExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java) ```gradle spotless { @@ -154,6 +154,9 @@ spotless { removeUnusedImports() + // Cleanthat will refactor your code, but it may break your style: apply it before your formatter + cleanthat() // has its own section below + // Choose one of these formatters. googleJavaFormat() // has its own section below eclipse() // has its own section below @@ -257,13 +260,30 @@ You can use `addTypeAnnotation()` and `removeTypeAnnotation()` to override its d You can make a pull request to add new annotations to Spotless's default list. +### cleanthat + +[homepage](https://github.com/solven-eu/cleanthat). CleanThat enables automatic refactoring of Java code. [ChangeLog](https://github.com/solven-eu/cleanthat/blob/master/CHANGES.MD) + +```gradle +spotless { + java { + cleanthat() + // optional: you can specify a specific version and/or config file + cleanthat() + .groupArtifact('1.7') // default is 'io.github.solven-eu.cleanthat:java' + .version('2.1') // You may force a past of -SNAPSHOT + .sourceCompatibility('1.7') // default is '1.7' + .addMutator('your.custom.MagicMutator') + .excludeMutator('UseCollectionIsEmpty') +``` + ## Groovy -- `com.diffplug.gradle.spotless.GroovyExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/GroovyExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java) -- `com.diffplug.gradle.spotless.GroovyGradleExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/GroovyGradleExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java) +- `com.diffplug.gradle.spotless.GroovyExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/GroovyExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java) +- `com.diffplug.gradle.spotless.GroovyGradleExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/GroovyGradleExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java) Configuration for Groovy is similar to [Java](#java), in that it also supports `licenseHeader` and `importOrder`. @@ -314,8 +334,8 @@ Groovy-Eclipse formatting errors/warnings lead per default to a build failure. T ## Kotlin -- `com.diffplug.gradle.spotless.KotlinExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/KotlinExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java) -- `com.diffplug.gradle.spotless.KotlinGradleExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/KotlinGradleExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java) +- `com.diffplug.gradle.spotless.KotlinExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/KotlinExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java) +- `com.diffplug.gradle.spotless.KotlinGradleExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/KotlinGradleExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java) ```gradle spotless { // if you are using build.gradle.kts, instead of 'spotless {' use: @@ -365,7 +385,7 @@ spotless { ktlint("0.45.2") .setUseExperimental(true) .userData(mapOf("android" to "true")) - .editorConfigPath("$projectDir/config/.editorconfig") // sample unusual placement + .setEditorConfigPath("$projectDir/config/.editorconfig") // sample unusual placement .editorConfigOverride(mapOf("indent_size" to 2)) } } @@ -386,7 +406,7 @@ spotless { ## Scala -`com.diffplug.gradle.spotless.ScalaExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/ScalaExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java) +`com.diffplug.gradle.spotless.ScalaExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/ScalaExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java) ```gradle spotless { @@ -418,7 +438,7 @@ spotless { ## C/C++ -`com.diffplug.gradle.spotless.CppExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/CppExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java) +`com.diffplug.gradle.spotless.CppExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/CppExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java) ```gradle spotless { @@ -450,7 +470,7 @@ spotles { ## Python -`com.diffplug.gradle.spotless.PythonExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/PythonExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/PythonExtension.java) +`com.diffplug.gradle.spotless.PythonExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/PythonExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/PythonExtension.java) ```gradle spotless { @@ -484,7 +504,7 @@ black().pathToExe('C:/myuser/.pyenv/versions/3.8.0/scripts/black.exe') ## FreshMark -`com.diffplug.gradle.spotless.FreshMarkExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FreshMarkExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FreshMarkExtension.java) +`com.diffplug.gradle.spotless.FreshMarkExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FreshMarkExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FreshMarkExtension.java) [homepage](https://github.com/diffplug/freshmark). [changelog](https://github.com/diffplug/freshmark/blob/master/CHANGES.md). FreshMark lets you generate markdown in the comments of your markdown. This helps to keep badges and links up-to-date (see the source for this file), and can also be helpful for generating complex tables (see the source for [the parent readme](../README.md)). @@ -505,7 +525,7 @@ spotless { ## Antlr4 -`com.diffplug.gradle.spotless.Antlr4Extension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/Antlr4Extension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/Antlr4Extension.java) +`com.diffplug.gradle.spotless.Antlr4Extension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/Antlr4Extension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/Antlr4Extension.java) ```gradle spotless { @@ -530,7 +550,7 @@ antlr4formatter('1.2.1') // version is optional ## SQL -`com.diffplug.gradle.spotless.SqlExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/SqlExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java) +`com.diffplug.gradle.spotless.SqlExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/SqlExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java) ```gradle spotless { @@ -570,7 +590,7 @@ sql.formatter.indent.size=4 ## Typescript -- `com.diffplug.gradle.spotless.TypescriptExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/TypescriptExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java) +- `com.diffplug.gradle.spotless.TypescriptExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/TypescriptExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java) ```gradle spotless { @@ -662,7 +682,7 @@ For details, see the [npm detection](#npm-detection) and [`.npmrc` detection](#n ## Javascript -- `com.diffplug.gradle.spotless.JavascriptExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/JavascriptExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java) +- `com.diffplug.gradle.spotless.JavascriptExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/JavascriptExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java) ```gradle spotless { @@ -726,7 +746,7 @@ For details, see the [npm detection](#npm-detection) and [`.npmrc` detection](#n ## JSON -- `com.diffplug.gradle.spotless.JsonExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/JsonExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java) +- `com.diffplug.gradle.spotless.JsonExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/JsonExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java) ```gradle spotless { @@ -801,7 +821,7 @@ spotless { ## YAML -- `com.diffplug.gradle.spotless.YamlExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/JsonExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java) +- `com.diffplug.gradle.spotless.YamlExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/JsonExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java) ```gradle spotless { @@ -1014,7 +1034,7 @@ Once a file's license header has a valid year, whether it is a year (`2020`) or * `2017` -> `2017-2020` * `2017-2019` -> `2017-2020` -See the [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.LicenseHeaderConfig.html) for a complete listing of options. +See the [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.LicenseHeaderConfig.html) for a complete listing of options. @@ -1087,9 +1107,9 @@ spotless { custom 'lowercase', { str -> str.toLowerCase() } ``` -However, custom rules will disable up-to-date checking and caching, unless you read [this javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html#bumpThisNumberIfACustomStepChanges-int-) and follow its instructions carefully. +However, custom rules will disable up-to-date checking and caching, unless you read [this javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html#bumpThisNumberIfACustomStepChanges-int-) and follow its instructions carefully. -Another option is to create proper `FormatterStep` in your `buildSrc`, and then call [`addStep`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html#addStep-com.diffplug.spotless.FormatterStep-). The contributing guide describes [how to do this](https://github.com/diffplug/spotless/blob/main/CONTRIBUTING.md#how-to-add-a-new-formatterstep). If the step is generally-useful, we hope you'll open a PR to share it! +Another option is to create proper `FormatterStep` in your `buildSrc`, and then call [`addStep`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html#addStep-com.diffplug.spotless.FormatterStep-). The contributing guide describes [how to do this](https://github.com/diffplug/spotless/blob/main/CONTRIBUTING.md#how-to-add-a-new-formatterstep). If the step is generally-useful, we hope you'll open a PR to share it! ```gradle @@ -1122,11 +1142,11 @@ spotless { format 'foo', com.acme.FooLanguageExtension, { ``` -If you'd like to create a one-off Spotless task outside of the `check`/`apply` framework, see [`FormatExtension.createIndependentApplyTask`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html#createIndependentApplyTask-java.lang.String-). +If you'd like to create a one-off Spotless task outside of the `check`/`apply` framework, see [`FormatExtension.createIndependentApplyTask`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html#createIndependentApplyTask-java.lang.String-). ## Inception (languages within languages within...) -In very rare cases, you might want to format e.g. javascript which is written inside JSP templates, or maybe java within a markdown file, or something wacky like that. You can specify hunks within a file using either open/close tags or a regex with a single capturing group, and then specify rules within it, like so. See [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.14.1/com/diffplug/gradle/spotless/FormatExtension.html#withinBlocks-java.lang.String-java.lang.String-java.lang.String-org.gradle.api.Action-) for more details. +In very rare cases, you might want to format e.g. javascript which is written inside JSP templates, or maybe java within a markdown file, or something wacky like that. You can specify hunks within a file using either open/close tags or a regex with a single capturing group, and then specify rules within it, like so. See [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.15.0/com/diffplug/gradle/spotless/FormatExtension.html#withinBlocks-java.lang.String-java.lang.String-java.lang.String-org.gradle.api.Action-) for more details. ```gradle import com.diffplug.gradle.spotless.JavaExtension diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java index 0f5926ec66..f351dd6188 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.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. @@ -19,6 +19,7 @@ import java.io.File; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Objects; @@ -34,6 +35,7 @@ import com.diffplug.spotless.extra.EclipseBasedStepBuilder; import com.diffplug.spotless.extra.java.EclipseJdtFormatterStep; import com.diffplug.spotless.generic.LicenseHeaderStep; +import com.diffplug.spotless.java.CleanthatJavaStep; import com.diffplug.spotless.java.FormatAnnotationsStep; import com.diffplug.spotless.java.GoogleJavaFormatStep; import com.diffplug.spotless.java.ImportOrderStep; @@ -270,6 +272,91 @@ private FormatterStep createStep() { } } + /** Apply CleanThat refactoring rules. */ + public CleanthatJavaConfig cleanthat() { + return new CleanthatJavaConfig(); + } + + public class CleanthatJavaConfig { + private String groupArtifact = CleanthatJavaStep.defaultGroupArtifact(); + + private String version = CleanthatJavaStep.defaultVersion(); + + private String sourceJdk = CleanthatJavaStep.defaultSourceJdk(); + + private List mutators = new ArrayList<>(CleanthatJavaStep.defaultMutators()); + + private List excludedMutators = new ArrayList<>(CleanthatJavaStep.defaultExcludedMutators()); + + private boolean includeDraft = CleanthatJavaStep.defaultIncludeDraft(); + + CleanthatJavaConfig() { + addStep(createStep()); + } + + public CleanthatJavaConfig groupArtifact(String groupArtifact) { + Objects.requireNonNull(groupArtifact); + this.groupArtifact = groupArtifact; + replaceStep(createStep()); + return this; + } + + public CleanthatJavaConfig version(String version) { + Objects.requireNonNull(version); + this.version = version; + replaceStep(createStep()); + return this; + } + + public CleanthatJavaConfig sourceCompatibility(String jdkVersion) { + Objects.requireNonNull(jdkVersion); + this.sourceJdk = jdkVersion; + replaceStep(createStep()); + return this; + } + + // Especially useful to clear default mutators + public CleanthatJavaConfig clearMutators() { + this.mutators.clear(); + replaceStep(createStep()); + return this; + } + + // An id of a mutator (see IMutator.getIds()) or + // tThe fully qualified name of a class implementing eu.solven.cleanthat.engine.java.refactorer.meta.IMutator + public CleanthatJavaConfig addMutator(String mutator) { + this.mutators.add(mutator); + replaceStep(createStep()); + return this; + } + + public CleanthatJavaConfig addMutators(Collection mutators) { + this.mutators.addAll(mutators); + replaceStep(createStep()); + return this; + } + + // useful to exclude a mutator amongst the default list of mutators + public CleanthatJavaConfig excludeMutator(String mutator) { + this.excludedMutators.add(mutator); + replaceStep(createStep()); + return this; + } + + public CleanthatJavaConfig includeDraft(boolean includeDraft) { + this.includeDraft = includeDraft; + replaceStep(createStep()); + return this; + } + + private FormatterStep createStep() { + return CleanthatJavaStep.create( + groupArtifact, + version, + sourceJdk, mutators, excludedMutators, includeDraft, provisioner()); + } + } + /** If the user hasn't specified the files yet, we'll assume he/she means all of the java files. */ @Override protected void setupTask(SpotlessTask task) { diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/CleanthatJavaIntegrationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/CleanthatJavaIntegrationTest.java new file mode 100644 index 0000000000..581bfe89b2 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/CleanthatJavaIntegrationTest.java @@ -0,0 +1,44 @@ +/* + * 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.gradle.spotless; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +class CleanthatJavaIntegrationTest extends GradleIntegrationHarness { + @Test + void integration() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "", + "spotless {", + " java {", + " target file('test.java')", + " cleanthat()", + " .sourceCompatibility('11')", + " .addMutators(['LiteralsFirstInComparisons', 'OptionalNotEmpty'])", + " }", + "}"); + + setFile("test.java").toResource("java/cleanthat/MultipleMutators.dirty.java"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("test.java").sameAsResource("java/cleanthat/MultipleMutators.clean.java"); + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PrettierIntegrationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PrettierIntegrationTest.java index 4c458e3ca7..a6b2ea9dc8 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PrettierIntegrationTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PrettierIntegrationTest.java @@ -183,6 +183,50 @@ void usePhpCommunityPlugin() throws IOException { assertFile("php-example.php").sameAsResource("npm/prettier/plugins/php.clean"); } + /** + * This test is to ensure that we can have multiple prettier instances in one spotless config. + * + * @see Issue #1162 on github + */ + @Test + void usePhpAndJavaCommunityPlugin() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "def prettierConfigPhp = [:]", + "prettierConfigPhp['tabWidth'] = 3", + "prettierConfigPhp['parser'] = 'php'", + "def prettierPackagesPhp = [:]", + "prettierPackagesPhp['prettier'] = '2.0.5'", + "prettierPackagesPhp['@prettier/plugin-php'] = '0.14.2'", + "def prettierConfigJava = [:]", + "prettierConfigJava['tabWidth'] = 4", + "prettierConfigJava['parser'] = 'java'", + "def prettierPackagesJava = [:]", + "prettierPackagesJava['prettier'] = '2.0.5'", + "prettierPackagesJava['prettier-plugin-java'] = '0.8.0'", + "spotless {", + " format 'php', {", + " target 'php-example.php'", + " prettier(prettierPackagesPhp).config(prettierConfigPhp)", + " }", + " java {", + " target 'JavaTest.java'", + " prettier(prettierPackagesJava).config(prettierConfigJava)", + " }", + "}"); + setFile("php-example.php").toResource("npm/prettier/plugins/php.dirty"); + setFile("JavaTest.java").toResource("npm/prettier/plugins/java-test.dirty"); + final BuildResult spotlessApply = gradleRunner().forwardOutput().withArguments("--stacktrace", "--info", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + final BuildResult spotlessApply2 = gradleRunner().forwardOutput().withArguments("--stacktrace", "--info", "spotlessApply").build(); + Assertions.assertThat(spotlessApply2.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("php-example.php").sameAsResource("npm/prettier/plugins/php.clean"); + assertFile("JavaTest.java").sameAsResource("npm/prettier/plugins/java-test.clean"); + } + @Test void autodetectNpmrcFileConfig() throws IOException { setFile(".npmrc").toLines( diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 567d880f4f..c21ec5dcb9 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -3,6 +3,23 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`). ## [Unreleased] +### Fixed +* `` can now handle `Array` as a root element. ([#1585](https://github.com/diffplug/spotless/pull/1585)) +### Added +* `cleanthat` added `includeDraft` option, to include draft mutators from composite mutators ([#XXX](https://github.com/diffplug/spotless/pull/XXX)) +### Changes +* Bump default `cleanthat` version to latest `2.1` -> `2.6` ([#1569](https://github.com/diffplug/spotless/pull/1569) and [#1574](https://github.com/diffplug/spotless/pull/1574)) + +## [2.33.0] - 2023-02-10 +### Added +* CleanThat Java Refactorer. ([#1560](https://github.com/diffplug/spotless/pull/1560)) +### Fixed +* Allow multiple instances of the same npm-based formatter to be used simultaneously. E.g. use prettier for typescript + *and* Java (using the community prettier-plugin-java) without messing up their respective `node_module` dependencies. ([#1565](https://github.com/diffplug/spotless/pull/1565)) +* `ktfmt` default style uses correct continuation indent. ([#1562](https://github.com/diffplug/spotless/pull/1562)) +### Changes +* Bump default `ktfmt` version to latest `0.42` -> `0.43` ([#1561](https://github.com/diffplug/spotless/pull/1561)) +* Bump default `jackson` version to latest `2.14.1` -> `2.14.2` ([#1536](https://github.com/diffplug/spotless/pull/1536)) ## [2.32.0] - 2023-02-05 ### Added diff --git a/plugin-maven/README.md b/plugin-maven/README.md index 0787150ee1..51ea9c23d7 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -8,8 +8,8 @@ output = [ ].join('\n'); --> [![Maven central](https://img.shields.io/badge/mavencentral-com.diffplug.spotless%3Aspotless--maven--plugin-blue.svg)](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.diffplug.spotless%22%20AND%20a%3A%22spotless-maven-plugin%22) -[![Changelog](https://img.shields.io/badge/changelog-2.32.0-blue.svg)](CHANGES.md) -[![Javadoc](https://img.shields.io/badge/javadoc-here-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-maven-plugin/2.32.0/index.html) +[![Changelog](https://img.shields.io/badge/changelog-2.33.0-blue.svg)](CHANGES.md) +[![Javadoc](https://img.shields.io/badge/javadoc-here-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-maven-plugin/2.33.0/index.html) + + @@ -274,6 +277,25 @@ list of well-known type annotations. You can make a pull request to add new one In the future there will be mechanisms to add/remove annotations from the list. These mechanisms already exist for the Gradle plugin. +### Cleanthat + +[homepage](https://github.com/solven-eu/cleanthat). CleanThat enables automatic refactoring of Java code. [ChangeLog](https://github.com/solven-eu/cleanthat/blob/master/CHANGES.MD) + +```xml + + 2.0 + ${maven.compiler.source} + + * + + + LiteralsFirstInComparisons + + + OptionalNotEmpty + + +``` ## Groovy diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/CleanthatJava.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/CleanthatJava.java new file mode 100644 index 0000000000..4133409919 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/CleanthatJava.java @@ -0,0 +1,54 @@ +/* + * Copyright 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.maven.java; + +import java.util.List; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.java.CleanthatJavaStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; + +public class CleanthatJava implements FormatterStepFactory { + @Parameter + private String groupArtifact; + + @Parameter + private String version; + + // https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#source + @Parameter(property = "maven.compiler.source") + private String sourceJdk = CleanthatJavaStep.defaultSourceJdk(); + + @Parameter + private List mutators = CleanthatJavaStep.defaultMutators(); + + @Parameter + private List excludedMutators = CleanthatJavaStep.defaultExcludedMutators(); + + @Parameter + private boolean includeDraft = CleanthatJavaStep.defaultIncludeDraft(); + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig config) { + String groupArtifact = this.groupArtifact != null ? this.groupArtifact : CleanthatJavaStep.defaultGroupArtifact(); + String version = this.version != null ? this.version : CleanthatJavaStep.defaultVersion(); + + return CleanthatJavaStep.create(groupArtifact, version, sourceJdk, mutators, excludedMutators, includeDraft, config.getProvisioner()); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java index 0921a838b3..efdf0827db 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java @@ -79,6 +79,10 @@ public void addFormatAnnotations(FormatAnnotations formatAnnotations) { addStepFactory(formatAnnotations); } + public void addCleanthat(CleanthatJava cleanthat) { + addStepFactory(cleanthat); + } + private static String fileMask(Path path) { String dir = path.toString(); if (!dir.endsWith(File.separator)) { diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java index fdc4a745a1..2891ee3b2c 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java @@ -285,7 +285,7 @@ private static String getSystemProperty(String name) { return value; } - private static String[] groupWithSteps(String group, String[] includes, String... steps) { + protected static String[] groupWithSteps(String group, String[] includes, String... steps) { String[] result = new String[steps.length + includes.length + 2]; result[0] = "<" + group + ">"; System.arraycopy(includes, 0, result, 1, includes.length); @@ -294,15 +294,22 @@ private static String[] groupWithSteps(String group, String[] includes, String.. return result; } - private static String[] groupWithSteps(String group, String... steps) { + protected static String[] groupWithSteps(String group, String... steps) { return groupWithSteps(group, new String[]{}, steps); } - private static String[] including(String... includes) { + protected static String[] including(String... includes) { return groupWithSteps("includes", groupWithSteps("include", includes)); } - private static String[] formats(String... formats) { + protected static String[] formats(String... formats) { return groupWithSteps("formats", formats); } + + protected static String[] formats(String[]... formats) { + String[] formatsArray = Arrays.stream(formats) + .flatMap(Arrays::stream) + .toArray(String[]::new); + return formats(formatsArray); + } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/CleanthatJavaRefactorerTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/CleanthatJavaRefactorerTest.java new file mode 100644 index 0000000000..379397f55b --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/CleanthatJavaRefactorerTest.java @@ -0,0 +1,114 @@ +/* + * 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.maven.java; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +class CleanthatJavaRefactorerTest extends MavenIntegrationHarness { + private static final Logger LOGGER = LoggerFactory.getLogger(CleanthatJavaRefactorerTest.class); + + @Test + void testEnableDraft() throws Exception { + writePomWithJavaSteps( + "", + " 11", + " true", + ""); + + runTest("MultipleMutators.dirty.java", "MultipleMutators.clean.onlyOptionalIsPresent.java"); + } + + @Test + void testLiteralsFirstInComparisons() throws Exception { + writePomWithJavaSteps( + "", + " ", + " LiteralsFirstInComparisons", + " ", + ""); + + runTest("LiteralsFirstInComparisons.dirty.java", "LiteralsFirstInComparisons.clean.java"); + } + + @Test + void testMultipleMutators_defaultIsJdk7() throws Exception { + // OptionalNotEmpty will be excluded as it is not compatible with JDK7 + writePomWithJavaSteps( + "", + " ", + " LiteralsFirstInComparisons", + " OptionalNotEmpty", + " ", + ""); + + runTest("MultipleMutators.dirty.java", "MultipleMutators.clean.onlyLiteralsFirst.java"); + } + + @Test + void testMultipleMutators_Jdk11IntroducedOptionalisPresent() throws Exception { + writePomWithJavaSteps( + "", + " 11", + " ", + " LiteralsFirstInComparisons", + " OptionalNotEmpty", + " ", + ""); + + runTest("MultipleMutators.dirty.java", "MultipleMutators.clean.java"); + } + + @Test + void testExcludeOptionalNotEmpty() throws Exception { + writePomWithJavaSteps( + "", + " ", + " LiteralsFirstInComparisons", + " OptionalNotEmpty", + " ", + " ", + " OptionalNotEmpty", + " ", + ""); + + runTest("MultipleMutators.dirty.java", "MultipleMutators.clean.onlyLiteralsFirst.java"); + } + + @Test + void testIncludeOnlyLiteralsFirstInComparisons() throws Exception { + writePomWithJavaSteps( + "", + " ", + " LiteralsFirstInComparisons", + " ", + ""); + + runTest("MultipleMutators.dirty.java", "MultipleMutators.clean.onlyLiteralsFirst.java"); + } + + private void runTest(String dirtyPath, String cleanPath) throws Exception { + String path = "src/main/java/test.java"; + setFile(path).toResource("java/cleanthat/" + dirtyPath); + // .withRemoteDebug(21654) + Assertions.assertThat(mavenRunner().withArguments("spotless:apply").runNoError().stdOutUtf8()).doesNotContain("[ERROR]"); + assertFile(path).sameAsResource("java/cleanthat/" + cleanPath); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/kotlin/KtfmtTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/kotlin/KtfmtTest.java index 4ae266cc23..e452c5d56b 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/kotlin/KtfmtTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/kotlin/KtfmtTest.java @@ -36,6 +36,15 @@ void testKtfmt() throws Exception { assertFile(path2).sameAsResource("kotlin/ktfmt/basic.clean"); } + @Test + void testContinuation() throws Exception { + writePomWithKotlinSteps(""); + + setFile("src/main/kotlin/main.kt").toResource("kotlin/ktfmt/continuation.dirty"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("src/main/kotlin/main.kt").sameAsResource("kotlin/ktfmt/continuation.clean"); + } + @Test void testKtfmtStyle() throws Exception { writePomWithKotlinSteps(""); diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java index 47dab5d152..78a0fc30ff 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java @@ -106,6 +106,58 @@ void unique_dependency_config() throws Exception { assertThat(result.stdOutUtf8()).contains(Prettier.ERROR_MESSAGE_ONLY_ONE_CONFIG); } + /** + * This test is to ensure that we can have multiple prettier instances in one spotless config. + * + * @see Issue #1162 on github + */ + @Test + void multiple_prettier_configs() throws Exception { + writePom( + formats( + groupWithSteps("format", including("php-example.php"), + "", + " ", + " ", + " prettier", + " 2.0.5", + " ", + " ", + " @prettier/plugin-php", + " 0.14.2", + " ", + " ", + " ", + " 3", + " php", + " ", + ""), + groupWithSteps("java", including("JavaTest.java"), + "", + " ", + " ", + " prettier", + " 2.0.5", + " ", + " ", + " prettier-plugin-java", + " 0.8.0", + " ", + " ", + " ", + " 4", + " java", + " ", + ""))); + + setFile("php-example.php").toResource("npm/prettier/plugins/php.dirty"); + setFile("JavaTest.java").toResource("npm/prettier/plugins/java-test.dirty"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("php-example.php").sameAsResource("npm/prettier/plugins/php.clean"); + assertFile("JavaTest.java").sameAsResource("npm/prettier/plugins/java-test.clean"); + + } + @Test void custom_plugin() throws Exception { writePomWithFormatSteps( diff --git a/settings.gradle b/settings.gradle index 077fdd9336..fa7b81bdb1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,11 +6,11 @@ pluginManagement { } plugins { - id 'com.diffplug.spotless' version '6.14.1' apply false + id 'com.diffplug.spotless' version '6.15.0' apply false // https://plugins.gradle.org/plugin/com.gradle.plugin-publish id 'com.gradle.plugin-publish' version '1.1.0' apply false // https://github.com/gradle-nexus/publish-plugin/releases - id 'io.github.gradle-nexus.publish-plugin' version '1.1.0' apply false + id 'io.github.gradle-nexus.publish-plugin' version '1.2.0' apply false // https://github.com/spotbugs/spotbugs-gradle-plugin/releases id 'com.github.spotbugs' version '5.0.13' apply false // https://github.com/diffplug/spotless-changelog/blob/main/CHANGELOG.md diff --git a/testlib/src/main/resources/java/cleanthat/LiteralsFirstInComparisons.clean.java b/testlib/src/main/resources/java/cleanthat/LiteralsFirstInComparisons.clean.java new file mode 100644 index 0000000000..8bacfa58db --- /dev/null +++ b/testlib/src/main/resources/java/cleanthat/LiteralsFirstInComparisons.clean.java @@ -0,0 +1,8 @@ +package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me; + +public class LiteralsFirstInComparisonsCases { + + public boolean isHardcoded(String input) { + return "hardcoded".equals(input); + } +} diff --git a/testlib/src/main/resources/java/cleanthat/LiteralsFirstInComparisons.dirty.java b/testlib/src/main/resources/java/cleanthat/LiteralsFirstInComparisons.dirty.java new file mode 100644 index 0000000000..3a1e074c91 --- /dev/null +++ b/testlib/src/main/resources/java/cleanthat/LiteralsFirstInComparisons.dirty.java @@ -0,0 +1,8 @@ +package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me; + +public class LiteralsFirstInComparisonsCases { + + public boolean isHardcoded(String input) { + return input.equals("hardcoded"); + } +} diff --git a/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.java b/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.java new file mode 100644 index 0000000000..318e1efa15 --- /dev/null +++ b/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.java @@ -0,0 +1,14 @@ +package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me; + +import java.util.Optional; + +public class LiteralsFirstInComparisonsCases { + + public boolean isHardcoded(String input) { + return "hardcoded".equals(input); + } + + public boolean isPresent(Optional optional) { + return optional.isPresent(); + } +} diff --git a/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.onlyLiteralsFirst.java b/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.onlyLiteralsFirst.java new file mode 100644 index 0000000000..629d24504b --- /dev/null +++ b/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.onlyLiteralsFirst.java @@ -0,0 +1,14 @@ +package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me; + +import java.util.Optional; + +public class LiteralsFirstInComparisonsCases { + + public boolean isHardcoded(String input) { + return "hardcoded".equals(input); + } + + public boolean isPresent(Optional optional) { + return !optional.isEmpty(); + } +} diff --git a/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.onlyOptionalIsPresent.java b/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.onlyOptionalIsPresent.java new file mode 100644 index 0000000000..0829602dc1 --- /dev/null +++ b/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.onlyOptionalIsPresent.java @@ -0,0 +1,14 @@ +package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me; + +import java.util.Optional; + +public class LiteralsFirstInComparisonsCases { + + public boolean isHardcoded(String input) { + return input.equals("hardcoded"); + } + + public boolean isPresent(Optional optional) { + return optional.isPresent(); + } +} diff --git a/testlib/src/main/resources/java/cleanthat/MultipleMutators.dirty.java b/testlib/src/main/resources/java/cleanthat/MultipleMutators.dirty.java new file mode 100644 index 0000000000..8ac230cabc --- /dev/null +++ b/testlib/src/main/resources/java/cleanthat/MultipleMutators.dirty.java @@ -0,0 +1,14 @@ +package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me; + +import java.util.Optional; + +public class LiteralsFirstInComparisonsCases { + + public boolean isHardcoded(String input) { + return input.equals("hardcoded"); + } + + public boolean isPresent(Optional optional) { + return !optional.isEmpty(); + } +} diff --git a/testlib/src/main/resources/json/singletonArrayAfter_Jackson.json b/testlib/src/main/resources/json/singletonArrayAfter_Jackson.json new file mode 100644 index 0000000000..243bc2550b --- /dev/null +++ b/testlib/src/main/resources/json/singletonArrayAfter_Jackson.json @@ -0,0 +1 @@ +[ 1, 2, 3, 4 ] \ No newline at end of file diff --git a/testlib/src/main/resources/json/singletonArrayBefore.json b/testlib/src/main/resources/json/singletonArrayBefore.json index 8290d39198..18d09f95fe 100644 --- a/testlib/src/main/resources/json/singletonArrayBefore.json +++ b/testlib/src/main/resources/json/singletonArrayBefore.json @@ -1 +1 @@ -[ 1, 2, 3, 4 ] +[ 1 , 2, 3, 4 ] diff --git a/testlib/src/main/resources/kotlin/ktfmt/continuation.clean b/testlib/src/main/resources/kotlin/ktfmt/continuation.clean new file mode 100644 index 0000000000..11677928fc --- /dev/null +++ b/testlib/src/main/resources/kotlin/ktfmt/continuation.clean @@ -0,0 +1,6 @@ +fun myFunction() { + val location = + restTemplate.postForLocation( + "/v1/my-api", mapOf("name" to "some-name", "url" to "https://www.google.com")) + return location +} diff --git a/testlib/src/main/resources/kotlin/ktfmt/continuation.dirty b/testlib/src/main/resources/kotlin/ktfmt/continuation.dirty new file mode 100644 index 0000000000..3652274d12 --- /dev/null +++ b/testlib/src/main/resources/kotlin/ktfmt/continuation.dirty @@ -0,0 +1,4 @@ +fun myFunction() { + val location = restTemplate.postForLocation("/v1/my-api", mapOf("name" to "some-name", "url" to "https://www.google.com")) + return location +} diff --git a/testlib/src/test/java/com/diffplug/spotless/json/JacksonJsonStepTest.java b/testlib/src/test/java/com/diffplug/spotless/json/JacksonJsonStepTest.java new file mode 100644 index 0000000000..be681653e1 --- /dev/null +++ b/testlib/src/test/java/com/diffplug/spotless/json/JacksonJsonStepTest.java @@ -0,0 +1,40 @@ +/* + * 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. + * 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.json; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.StepHarness; +import com.diffplug.spotless.TestProvisioner; + +class JacksonJsonStepTest { + + private static final int INDENT = 4; + + private final FormatterStep step = JsonSimpleStep.create(INDENT, TestProvisioner.mavenCentral()); + private final StepHarness stepHarness = StepHarness.forStep(step); + + @Test + void canSetCustomIndentationLevel() { + FormatterStep step = JacksonJsonStep.create(TestProvisioner.mavenCentral()); + StepHarness stepHarness = StepHarness.forStep(step); + + String before = "json/singletonArrayBefore.json"; + String after = "json/singletonArrayAfter_Jackson.json"; + stepHarness.testResource(before, after); + } +}