From eae8ffc0fc20997bda6e540b4da448bf00a0d8d7 Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Sun, 15 Jan 2023 16:52:19 +0400 Subject: [PATCH 01/48] Add JSON jackson step, Refactor with Yaml, enable endWithEol, features are defined in a Map -> boolean --- lib/build.gradle | 1 + .../glue/json/JsonJacksonFormatterFunc.java | 103 ++++++++++++++++++ .../glue/yaml/YamlJacksonFormatterFunc.java | 77 ++----------- .../diffplug/spotless/json/JacksonConfig.java | 71 ++++++++++++ .../spotless/json/JacksonJsonStep.java | 79 ++++++++++++++ ...lJacksonStep.java => JacksonYamlStep.java} | 31 +++--- .../Jackson.java => json/JacksonJson.java} | 34 ++++-- .../diffplug/spotless/maven/json/Json.java | 4 + .../spotless/maven/yaml/JacksonYaml.java | 60 ++++++++++ .../diffplug/spotless/maven/yaml/Yaml.java | 2 +- .../spotless/maven/json/JsonTest.java | 23 +++- .../spotless/maven/yaml/YamlTest.java | 9 ++ .../json/sortByKeysAfter_Jackson.json | 19 ++++ ...fter_Jackson_noSpaceAfterKeySeparator.json | 19 ++++ .../array_with_bracket.clean_with_eol.yaml | 12 ++ 15 files changed, 441 insertions(+), 103 deletions(-) create mode 100644 lib/src/jackson/java/com/diffplug/spotless/glue/json/JsonJacksonFormatterFunc.java create mode 100644 lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java create mode 100644 lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java rename lib/src/main/java/com/diffplug/spotless/yaml/{YamlJacksonStep.java => JacksonYamlStep.java} (70%) rename plugin-maven/src/main/java/com/diffplug/spotless/maven/{yaml/Jackson.java => json/JacksonJson.java} (51%) create mode 100644 plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java create mode 100644 testlib/src/main/resources/json/sortByKeysAfter_Jackson.json create mode 100644 testlib/src/main/resources/json/sortByKeysAfter_Jackson_noSpaceAfterKeySeparator.json create mode 100644 testlib/src/main/resources/yaml/array_with_bracket.clean_with_eol.yaml diff --git a/lib/build.gradle b/lib/build.gradle index 3ef2064765..3c36aa0188 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -57,6 +57,7 @@ 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' String VER_KTFMT = '0.42' diff --git a/lib/src/jackson/java/com/diffplug/spotless/glue/json/JsonJacksonFormatterFunc.java b/lib/src/jackson/java/com/diffplug/spotless/glue/json/JsonJacksonFormatterFunc.java new file mode 100644 index 0000000000..0a13ab6626 --- /dev/null +++ b/lib/src/jackson/java/com/diffplug/spotless/glue/json/JsonJacksonFormatterFunc.java @@ -0,0 +1,103 @@ +/* + * 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.glue.json; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.json.JacksonConfig; + +/** + * A {@link FormatterFunc} based on Jackson library + */ +// https://github.com/FasterXML/jackson-dataformats-text/issues/372 +public class JsonJacksonFormatterFunc implements FormatterFunc { + private JacksonConfig jacksonConfig; + + public JsonJacksonFormatterFunc(JacksonConfig jacksonConfig) { + this.jacksonConfig = jacksonConfig; + } + + @Override + public String apply(String input) throws Exception { + ObjectMapper objectMapper = makeObjectMapper(); + + return format(objectMapper, input); + } + + /** + * @return a {@link JsonFactory}. May be overridden to handle alternative formats. + * @see jackson-dataformats-text + */ + protected JsonFactory makeJsonFactory() { + return new JsonFactory(); + } + + protected ObjectMapper makeObjectMapper() { + JsonFactory jsonFactory = makeJsonFactory(); + ObjectMapper objectMapper = new ObjectMapper(jsonFactory); + + // Configure the ObjectMapper + // https://github.com/FasterXML/jackson-databind#commonly-used-features + jacksonConfig.getFeatureToToggle().forEach((rawFeature, toggle) -> { + // https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection + SerializationFeature feature = SerializationFeature.valueOf(rawFeature); + + objectMapper.configure(feature, toggle); + }); + + return objectMapper; + } + + protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { + // We may consider adding manually an initial '---' prefix to help management of multiple documents + // if (!input.trim().startsWith("---")) { + // input = "---" + "\n" + input; + // } + + 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 + // 2023-01: For now, we get 'Cannot deserialize value of type `com.fasterxml.jackson.databind.node.ObjectNode` from Array value' + // JsonParser yamlParser = objectMapper.getFactory().createParser(input); + // List docs = objectMapper.readValues(yamlParser, ObjectNode.class).readAll(); + // return objectMapper.writeValueAsString(docs); + + // 2023-01: This returns JSON instead of YAML + // This will transit with a JsonNode + // A JsonNode may keep the comments from the input node + // JsonNode jsonNode = objectMapper.readTree(input); + //Not 'toPrettyString' as one could require no INDENT_OUTPUT + // return jsonNode.toPrettyString(); + ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class); + String outputFromjackson = objectMapper.writeValueAsString(objectNode); + + if (jacksonConfig.isEndWithEol() && !outputFromjackson.endsWith("\n")) { + outputFromjackson += "\n"; + } + + return outputFromjackson; + } catch (JsonProcessingException e) { + throw new AssertionError("Unable to format. input='" + input + "'", e); + } + } +} diff --git a/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/YamlJacksonFormatterFunc.java b/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/YamlJacksonFormatterFunc.java index 30cf538159..eda2675985 100644 --- a/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/YamlJacksonFormatterFunc.java +++ b/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/YamlJacksonFormatterFunc.java @@ -15,83 +15,20 @@ */ package com.diffplug.spotless.glue.yaml; -import java.io.IOException; -import java.util.List; +import com.diffplug.spotless.glue.json.JsonJacksonFormatterFunc; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.json.JacksonConfig; -public class YamlJacksonFormatterFunc implements FormatterFunc { - private List enabledFeatures; - private List disabledFeatures; +public class YamlJacksonFormatterFunc extends JsonJacksonFormatterFunc { - public YamlJacksonFormatterFunc(List enabledFeatures, List disabledFeatures) { - this.enabledFeatures = enabledFeatures; - this.disabledFeatures = disabledFeatures; + public YamlJacksonFormatterFunc(JacksonConfig jacksonConfig) { + super(jacksonConfig); } @Override - public String apply(String input) throws Exception { - ObjectMapper objectMapper = makeObjectMapper(); - - return format(objectMapper, input); - } - - protected ObjectMapper makeObjectMapper() { - YAMLFactory yamlFactory = new YAMLFactory(); - ObjectMapper objectMapper = new ObjectMapper(yamlFactory); - - // Configure the ObjectMapper - // https://github.com/FasterXML/jackson-databind#commonly-used-features - for (String rawFeature : enabledFeatures) { - // https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection - SerializationFeature feature = SerializationFeature.valueOf(rawFeature); - - objectMapper.enable(feature); - } - - for (String rawFeature : disabledFeatures) { - // https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection - SerializationFeature feature = SerializationFeature.valueOf(rawFeature); - - objectMapper.disable(feature); - } - return objectMapper; - } - - protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { - // We may consider adding manually an initial '---' prefix to help management of multiple documents - // if (!input.trim().startsWith("---")) { - // input = "---" + "\n" + input; - // } - - 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 - // 2023-01: For now, we get 'Cannot deserialize value of type `com.fasterxml.jackson.databind.node.ObjectNode` from Array value' - // JsonParser yamlParser = objectMapper.getFactory().createParser(input); - // List docs = objectMapper.readValues(yamlParser, ObjectNode.class).readAll(); - // return objectMapper.writeValueAsString(docs); - - // 2023-01: This returns JSON instead of YAML - // This will transit with a JsonNode - // A JsonNode may keep the comments from the input node - // JsonNode jsonNode = objectMapper.readTree(input); - //Not 'toPrettyString' as one could require no INDENT_OUTPUT - // return jsonNode.toPrettyString(); - ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class); - return objectMapper.writeValueAsString(objectNode); - } catch (JsonProcessingException e) { - throw new AssertionError("Unable to format YAML. input='" + input + "'", e); - } + protected YAMLFactory makeJsonFactory() { + return new YAMLFactory(); } - - // Spotbugs - private static class ObjectNodeTypeReference extends TypeReference {} } diff --git a/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java new file mode 100644 index 0000000000..526e235eb7 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java @@ -0,0 +1,71 @@ +/* + * 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.json; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A DTO holding the options for Jackson-based formatters + */ +public class JacksonConfig { + + private static final Map DEFAULT_FEATURE_TOGGLES; + + static { + Map defaultFeatureToggles = new LinkedHashMap<>(); + // We activate by default the PrettyPrinter from Jackson + defaultFeatureToggles.put("INDENT_OUTPUT", true); + DEFAULT_FEATURE_TOGGLES = defaultFeatureToggles; + } + + protected Map featureToToggle; + + // https://github.com/revelc/formatter-maven-plugin/pull/405 + protected boolean endWithEol = false; + + // https://github.com/revelc/formatter-maven-plugin/pull/280 + protected boolean spaceBeforeSeparator = false; + + public Map getFeatureToToggle() { + return Collections.unmodifiableMap(featureToToggle); + } + + public void setFeatureToToggle(Map featureToToggle) { + this.featureToToggle = featureToToggle; + } + + public void appendFeatureToToggle(Map features) { + this.featureToToggle.putAll(features); + } + + public boolean isEndWithEol() { + return endWithEol; + } + + public void setEndWithEol(boolean endWithEol) { + this.endWithEol = endWithEol; + } + + public boolean isSpaceBeforeSeparator() { + return spaceBeforeSeparator; + } + + public void setSpaceBeforeSeparator(boolean spaceBeforeSeparator) { + this.spaceBeforeSeparator = spaceBeforeSeparator; + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java b/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java new file mode 100644 index 0000000000..0fe1ab057b --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java @@ -0,0 +1,79 @@ +/* + * 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 java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Objects; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.JarState; +import com.diffplug.spotless.Provisioner; + +/** + * Simple YAML formatter which reformats the file according to Jackson YAMLFactory. + */ +// https://stackoverflow.com/questions/14515994/convert-json-string-to-pretty-print-json-output-using-jackson +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"; + + private JacksonJsonStep() {} + + public static String defaultVersion() { + return DEFAULT_VERSION; + } + + public static FormatterStep create(JacksonConfig jacksonConfig, + String jacksonVersion, + Provisioner provisioner) { + Objects.requireNonNull(provisioner, "provisioner cannot be null"); + return FormatterStep.createLazy("json", + () -> new State(jacksonConfig, jacksonVersion, provisioner), + State::toFormatter); + } + + public static FormatterStep create(Provisioner provisioner) { + return create(new JacksonConfig(), defaultVersion(), provisioner); + } + + private static final class State implements Serializable { + private static final long serialVersionUID = 1L; + + private final JacksonConfig jacksonConfig; + + private final JarState jarState; + + private State(JacksonConfig jacksonConfig, + String jacksonVersion, + Provisioner provisioner) throws IOException { + this.jacksonConfig = jacksonConfig; + + this.jarState = JarState.from(JacksonJsonStep.MAVEN_COORDINATE + jacksonVersion, provisioner); + } + + FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, + InstantiationException, IllegalAccessException { + Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.json.JacksonJsonFormatterFunc"); + Constructor constructor = formatterFunc.getConstructor(JacksonConfig.class); + return (FormatterFunc) constructor.newInstance(jacksonConfig); + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/yaml/YamlJacksonStep.java b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java similarity index 70% rename from lib/src/main/java/com/diffplug/spotless/yaml/YamlJacksonStep.java rename to lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java index db2525ab97..944d90e6c9 100644 --- a/lib/src/main/java/com/diffplug/spotless/yaml/YamlJacksonStep.java +++ b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java @@ -19,67 +19,62 @@ import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; -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.Provisioner; +import com.diffplug.spotless.json.JacksonConfig; /** * Simple YAML formatter which reformats the file according to Jackson YAMLFactory. */ // https://stackoverflow.com/questions/14515994/convert-json-string-to-pretty-print-json-output-using-jackson -public class YamlJacksonStep { +public class JacksonYamlStep { static final String MAVEN_COORDINATE = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:"; // https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml static final String DEFAULT_VERSION = "2.14.1"; - private YamlJacksonStep() {} + private JacksonYamlStep() {} public static String defaultVersion() { return DEFAULT_VERSION; } - public static FormatterStep create(List enabledFeatures, - List disabledFeatures, + public static FormatterStep create(JacksonConfig jacksonConfig, String jacksonVersion, Provisioner provisioner) { Objects.requireNonNull(provisioner, "provisioner cannot be null"); return FormatterStep.createLazy("yaml", - () -> new State(enabledFeatures, disabledFeatures, jacksonVersion, provisioner), + () -> new State(jacksonConfig, jacksonVersion, provisioner), State::toFormatter); } public static FormatterStep create(Provisioner provisioner) { - return create(Arrays.asList("INDENT_OUTPUT"), Arrays.asList(), defaultVersion(), provisioner); + return create(new JacksonConfig(), defaultVersion(), provisioner); } private static final class State implements Serializable { private static final long serialVersionUID = 1L; - private final List enabledFeatures; - private final List disabledFeatures; + private final JacksonConfig jacksonConfig; private final JarState jarState; - private State(List enabledFeatures, - List disabledFeatures, + private State(JacksonConfig jacksonConfig, String jacksonVersion, Provisioner provisioner) throws IOException { - this.enabledFeatures = enabledFeatures; - this.disabledFeatures = disabledFeatures; + this.jacksonConfig = jacksonConfig; - this.jarState = JarState.from(YamlJacksonStep.MAVEN_COORDINATE + jacksonVersion, provisioner); + this.jarState = JarState.from(JacksonYamlStep.MAVEN_COORDINATE + jacksonVersion, provisioner); } FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { - Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.yaml.YamlJacksonFormatterFunc"); - Constructor constructor = formatterFunc.getConstructor(List.class, List.class); - return (FormatterFunc) constructor.newInstance(enabledFeatures, disabledFeatures); + Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.yaml.JacksonYamlFormatterFunc"); + Constructor constructor = formatterFunc.getConstructor(JacksonConfig.class); + return (FormatterFunc) constructor.newInstance(jacksonConfig); } } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/Jackson.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java similarity index 51% rename from plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/Jackson.java rename to plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java index 2bc7617a38..cf03ef21ca 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/Jackson.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java @@ -13,34 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.diffplug.spotless.maven.yaml; +package com.diffplug.spotless.maven.json; -import java.util.Arrays; -import java.util.List; +import java.util.Collections; +import java.util.Map; import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.json.JacksonConfig; +import com.diffplug.spotless.json.JacksonJsonStep; +import com.diffplug.spotless.maven.FormatterFactory; import com.diffplug.spotless.maven.FormatterStepConfig; import com.diffplug.spotless.maven.FormatterStepFactory; -import com.diffplug.spotless.yaml.YamlJacksonStep; -public class Jackson implements FormatterStepFactory { +/** + * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element. + */ +public class JacksonJson implements FormatterStepFactory { @Parameter - private String version = YamlJacksonStep.defaultVersion(); + String version = JacksonJsonStep.defaultVersion(); @Parameter - private String[] enabledFeatures = new String[]{"INDENT_OUTPUT"}; + boolean endWithEol = new JacksonConfig().isEndWithEol(); @Parameter - private String[] disabledFeatures = new String[0]; + private Map features = Collections.emptyMap(); @Override public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { - List enabledFeaturesAsList = Arrays.asList(enabledFeatures); - List disabledFeaturesAsList = Arrays.asList(disabledFeatures); - return YamlJacksonStep - .create(enabledFeaturesAsList, disabledFeaturesAsList, version, stepConfig.getProvisioner()); + JacksonConfig jacksonConfig = new JacksonConfig(); + + if (features != null) { + jacksonConfig.appendFeatureToToggle(features); + } + jacksonConfig.setEndWithEol(endWithEol); + + return JacksonJsonStep + .create(jacksonConfig, version, stepConfig.getProvisioner()); } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Json.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Json.java index c326525d0c..852088278a 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Json.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Json.java @@ -44,4 +44,8 @@ public void addGson(Gson gson) { addStepFactory(gson); } + public void addJackson(JacksonJson jackson) { + addStepFactory(jackson); + } + } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java new file mode 100644 index 0000000000..d3d9558cf4 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java @@ -0,0 +1,60 @@ +/* + * 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.yaml; + +import java.util.Collections; +import java.util.Map; + +import com.diffplug.spotless.maven.FormatterFactory; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.json.JacksonConfig; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; +import com.diffplug.spotless.yaml.JacksonYamlStep; + +/** + * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element. + */ +public class JacksonYaml implements FormatterStepFactory { + + @Parameter + private String version = JacksonYamlStep.defaultVersion(); + + @Parameter + boolean endWithEol = new JacksonConfig().isEndWithEol(); + + @Parameter + boolean spaceBeforeSeparator = new JacksonConfig().isSpaceBeforeSeparator(); + + @Parameter + private Map features = Collections.emptyMap(); + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { + JacksonConfig jacksonConfig = new JacksonConfig(); + + if (features != null) { + jacksonConfig.appendFeatureToToggle(features); + } + jacksonConfig.setEndWithEol(endWithEol); + + return JacksonYamlStep + .create(jacksonConfig, version, stepConfig.getProvisioner()); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/Yaml.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/Yaml.java index 6cba1b2ebd..a6ceaaa592 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/Yaml.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/Yaml.java @@ -34,7 +34,7 @@ public String licenseHeaderDelimiter() { return null; } - public void addJackson(Jackson jackson) { + public void addJackson(JacksonYaml jackson) { addStepFactory(jackson); } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java index e492109faa..90b3a2f077 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java @@ -58,8 +58,6 @@ public void testFormatJson_WithGson_sortByKeys() throws Exception { setFile("json_test.json").toResource("json/sortByKeysBefore.json"); String output = mavenRunner().withArguments("spotless:apply").runNoError().output(); - LOGGER.error(output); - System.err.println(output); assertFile("json_test.json").sameAsResource("json/sortByKeysAfter.json"); } @@ -72,4 +70,25 @@ public void testFormatJson_WithGson_defaultConfig_nestedObject() throws Exceptio assertFile("json_test.json").sameAsResource("json/nestedObjectAfter.json"); } + + @Test + public void testFormatJson_WithJackson_sortByKeys() throws Exception { + writePomWithJsonSteps("true"); + + setFile("json_test.json").toResource("json/sortByKeysBefore.json"); + + String output = mavenRunner().withArguments("spotless:apply").runNoError().output(); + assertFile("json_test.json").sameAsResource("json/sortByKeysAfter.json"); + } + + @Test + public void testFormatJson_WithJackson_sortByKeys_noSpaceAfterKeySeparator() throws Exception { + writePomWithJsonSteps("falsetrue"); + + setFile("json_test.json").toResource("json/sortByKeysBefore.json"); + + String output = mavenRunner().withArguments("spotless:apply").runNoError().output(); + assertFile("json_test.json").sameAsResource("json/sortByKeysAfter.json"); + } + } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java index b5a417c536..15366ab2e6 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java @@ -48,6 +48,15 @@ public void testFormatYaml_WithJackson_defaultConfig_arrayBrackets() throws Exce assertFile("yaml_test.yaml").sameAsResource("yaml/array_with_bracket.clean.yaml"); } + @Test + public void testFormatYaml_WithJackson_defaultConfig_arrayBrackets_withEol() throws Exception { + writePomWithYamlSteps("true"); + + setFile("yaml_test.yaml").toResource("yaml/array_with_bracket.yaml"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("yaml_test.yaml").sameAsResource("yaml/array_with_bracket.clean_with_eol.yaml"); + } + @Test public void testFormatYaml_WithJackson_defaultConfig_multipleDocuments() throws Exception { writePomWithYamlSteps(""); diff --git a/testlib/src/main/resources/json/sortByKeysAfter_Jackson.json b/testlib/src/main/resources/json/sortByKeysAfter_Jackson.json new file mode 100644 index 0000000000..c4a48de2f2 --- /dev/null +++ b/testlib/src/main/resources/json/sortByKeysAfter_Jackson.json @@ -0,0 +1,19 @@ +{ + "A": 1, + "X": 2, + "_arraysNotSorted": [ + 3, + 2, + 1 + ], + "a": 3, + "c": 4, + "x": 5, + "z": { + "A": 1, + "X": 2, + "a": 3, + "c": 4, + "x": 5 + } +} diff --git a/testlib/src/main/resources/json/sortByKeysAfter_Jackson_noSpaceAfterKeySeparator.json b/testlib/src/main/resources/json/sortByKeysAfter_Jackson_noSpaceAfterKeySeparator.json new file mode 100644 index 0000000000..c4a48de2f2 --- /dev/null +++ b/testlib/src/main/resources/json/sortByKeysAfter_Jackson_noSpaceAfterKeySeparator.json @@ -0,0 +1,19 @@ +{ + "A": 1, + "X": 2, + "_arraysNotSorted": [ + 3, + 2, + 1 + ], + "a": 3, + "c": 4, + "x": 5, + "z": { + "A": 1, + "X": 2, + "a": 3, + "c": 4, + "x": 5 + } +} diff --git a/testlib/src/main/resources/yaml/array_with_bracket.clean_with_eol.yaml b/testlib/src/main/resources/yaml/array_with_bracket.clean_with_eol.yaml new file mode 100644 index 0000000000..6080ba1f82 --- /dev/null +++ b/testlib/src/main/resources/yaml/array_with_bracket.clean_with_eol.yaml @@ -0,0 +1,12 @@ +--- +episodes: +- 1 +- 2 +- 3 +- 4 +- 5 +- 6 +- 7 +best-jedi: + name: "Obi-Wan" + side: "light" From 65e74655e9191d1c891cafb6e3c5683da35bb041 Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Mon, 16 Jan 2023 21:17:46 +0400 Subject: [PATCH 02/48] Add doc, Improve tests --- CHANGES.md | 2 + ...unc.java => JacksonJsonFormatterFunc.java} | 53 +++++++++++++-- .../glue/yaml/JacksonYamlFormatterFunc.java | 66 +++++++++++++++++++ .../glue/yaml/YamlJacksonFormatterFunc.java | 34 ---------- .../diffplug/spotless/json/JacksonConfig.java | 27 ++++---- plugin-maven/CHANGES.md | 5 ++ plugin-maven/README.md | 12 ++-- .../spotless/maven/json/JacksonJson.java | 10 ++- .../spotless/maven/yaml/JacksonYaml.java | 14 ++-- .../spotless/maven/json/JsonTest.java | 9 ++- .../spotless/maven/yaml/YamlTest.java | 6 +- ...After_Jackson_spaceAfterKeySeparator.json} | 0 ...h_bracket.clean.spaceBeforeSeparator.yaml} | 2 +- 13 files changed, 156 insertions(+), 84 deletions(-) rename lib/src/jackson/java/com/diffplug/spotless/glue/json/{JsonJacksonFormatterFunc.java => JacksonJsonFormatterFunc.java} (70%) create mode 100644 lib/src/jackson/java/com/diffplug/spotless/glue/yaml/JacksonYamlFormatterFunc.java delete mode 100644 lib/src/jackson/java/com/diffplug/spotless/glue/yaml/YamlJacksonFormatterFunc.java rename testlib/src/main/resources/json/{sortByKeysAfter_Jackson_noSpaceAfterKeySeparator.json => sortByKeysAfter_Jackson_spaceAfterKeySeparator.json} (100%) rename testlib/src/main/resources/yaml/{array_with_bracket.clean_with_eol.yaml => array_with_bracket.clean.spaceBeforeSeparator.yaml} (81%) diff --git a/CHANGES.md b/CHANGES.md index c08ca86796..fd10d2c14e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,8 @@ 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] +### Changes +* Rename `YamlJacksonStep` into `JacksonYamlStep` while normalizing Jackson usage ([#1492](https://github.com/diffplug/spotless/pull/1492)) ## [2.32.0] - 2023-01-13 ### Added diff --git a/lib/src/jackson/java/com/diffplug/spotless/glue/json/JsonJacksonFormatterFunc.java b/lib/src/jackson/java/com/diffplug/spotless/glue/json/JacksonJsonFormatterFunc.java similarity index 70% rename from lib/src/jackson/java/com/diffplug/spotless/glue/json/JsonJacksonFormatterFunc.java rename to lib/src/jackson/java/com/diffplug/spotless/glue/json/JacksonJsonFormatterFunc.java index 0a13ab6626..c19e56b549 100644 --- a/lib/src/jackson/java/com/diffplug/spotless/glue/json/JsonJacksonFormatterFunc.java +++ b/lib/src/jackson/java/com/diffplug/spotless/glue/json/JacksonJsonFormatterFunc.java @@ -16,9 +16,14 @@ package com.diffplug.spotless.glue.json; import java.io.IOException; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.core.util.Separators; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -30,10 +35,10 @@ * A {@link FormatterFunc} based on Jackson library */ // https://github.com/FasterXML/jackson-dataformats-text/issues/372 -public class JsonJacksonFormatterFunc implements FormatterFunc { +public class JacksonJsonFormatterFunc implements FormatterFunc { private JacksonConfig jacksonConfig; - public JsonJacksonFormatterFunc(JacksonConfig jacksonConfig) { + public JacksonJsonFormatterFunc(JacksonConfig jacksonConfig) { this.jacksonConfig = jacksonConfig; } @@ -56,6 +61,8 @@ protected ObjectMapper makeObjectMapper() { JsonFactory jsonFactory = makeJsonFactory(); ObjectMapper objectMapper = new ObjectMapper(jsonFactory); + objectMapper.setDefaultPrettyPrinter(makePrinter()); + // Configure the ObjectMapper // https://github.com/FasterXML/jackson-databind#commonly-used-features jacksonConfig.getFeatureToToggle().forEach((rawFeature, toggle) -> { @@ -68,6 +75,44 @@ protected ObjectMapper makeObjectMapper() { return objectMapper; } + protected DefaultPrettyPrinter makePrinter() { + boolean spaceBeforeSeparator = jacksonConfig.isSpaceBeforeSeparator(); + + DefaultPrettyPrinter.Indenter indenter = new DefaultIndenter(getIndentation(), "\n"); + DefaultPrettyPrinter printer = new DefaultPrettyPrinter() { + private static final long serialVersionUID = 1L; + + @Override + public DefaultPrettyPrinter createInstance() { + return new DefaultPrettyPrinter(this); + } + + @Override + public DefaultPrettyPrinter withSeparators(Separators separators) { + this._separators = separators; + if (spaceBeforeSeparator) { + this._objectFieldValueSeparatorWithSpaces = " " + separators.getObjectFieldValueSeparator() + " "; + } else { + this._objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " "; + } + return this; + } + }; + + printer.indentObjectsWith(indenter); + printer.indentArraysWith(indenter); + return printer; + } + + protected String getIndentation() { + int indentSpaces = jacksonConfig.getIndentSpaces(); + if (indentSpaces < 0) { + return "\t"; + } else { + return IntStream.range(0, indentSpaces).mapToObj(i -> "").collect(Collectors.joining()); + } + } + protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { // We may consider adding manually an initial '---' prefix to help management of multiple documents // if (!input.trim().startsWith("---")) { @@ -91,10 +136,6 @@ protected String format(ObjectMapper objectMapper, String input) throws IllegalA ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class); String outputFromjackson = objectMapper.writeValueAsString(objectNode); - if (jacksonConfig.isEndWithEol() && !outputFromjackson.endsWith("\n")) { - outputFromjackson += "\n"; - } - return outputFromjackson; } catch (JsonProcessingException e) { throw new AssertionError("Unable to format. input='" + input + "'", e); 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 new file mode 100644 index 0000000000..6a471a884c --- /dev/null +++ b/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/JacksonYamlFormatterFunc.java @@ -0,0 +1,66 @@ +/* + * 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.glue.yaml; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; + +import com.diffplug.spotless.glue.json.JacksonJsonFormatterFunc; +import com.diffplug.spotless.json.JacksonConfig; + +public class JacksonYamlFormatterFunc extends JacksonJsonFormatterFunc { + + public JacksonYamlFormatterFunc(JacksonConfig jacksonConfig) { + super(jacksonConfig); + } + + @Override + protected YAMLFactory makeJsonFactory() { + return new YAMLFactory(); + } + + @Override + protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { + // We may consider adding manually an initial '---' prefix to help management of multiple documents + // if (!input.trim().startsWith("---")) { + // input = "---" + "\n" + input; + // } + + 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 + // 2023-01: For now, we get 'Cannot deserialize value of type `com.fasterxml.jackson.databind.node.ObjectNode` from Array value' + // JsonParser yamlParser = objectMapper.getFactory().createParser(input); + // List docs = objectMapper.readValues(yamlParser, ObjectNode.class).readAll(); + // return objectMapper.writeValueAsString(docs); + + // A JsonNode may keep the comments from the input node + // JsonNode jsonNode = objectMapper.readTree(input); + //Not 'toPrettyString' as one could require no INDENT_OUTPUT + // return jsonNode.toPrettyString(); + ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class); + String outputFromjackson = objectMapper.writeValueAsString(objectNode); + + return outputFromjackson; + } catch (JsonProcessingException e) { + throw new AssertionError("Unable to format. input='" + input + "'", e); + } + } +} diff --git a/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/YamlJacksonFormatterFunc.java b/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/YamlJacksonFormatterFunc.java deleted file mode 100644 index eda2675985..0000000000 --- a/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/YamlJacksonFormatterFunc.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.glue.yaml; - -import com.diffplug.spotless.glue.json.JsonJacksonFormatterFunc; - -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; - -import com.diffplug.spotless.json.JacksonConfig; - -public class YamlJacksonFormatterFunc extends JsonJacksonFormatterFunc { - - public YamlJacksonFormatterFunc(JacksonConfig jacksonConfig) { - super(jacksonConfig); - } - - @Override - protected YAMLFactory makeJsonFactory() { - return new YAMLFactory(); - } -} diff --git a/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java index 526e235eb7..2fadf31d78 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java @@ -15,6 +15,7 @@ */ package com.diffplug.spotless.json; +import java.io.Serializable; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -22,7 +23,7 @@ /** * A DTO holding the options for Jackson-based formatters */ -public class JacksonConfig { +public class JacksonConfig implements Serializable { private static final Map DEFAULT_FEATURE_TOGGLES; @@ -33,14 +34,14 @@ public class JacksonConfig { DEFAULT_FEATURE_TOGGLES = defaultFeatureToggles; } - protected Map featureToToggle; - - // https://github.com/revelc/formatter-maven-plugin/pull/405 - protected boolean endWithEol = false; + protected Map featureToToggle = new LinkedHashMap<>(); // https://github.com/revelc/formatter-maven-plugin/pull/280 + // By default, Jackson adds a ' ' before separator, which is not standard with most IDE/JSON libraries protected boolean spaceBeforeSeparator = false; + // protected int indentSpaces; + public Map getFeatureToToggle() { return Collections.unmodifiableMap(featureToToggle); } @@ -53,14 +54,6 @@ public void appendFeatureToToggle(Map features) { this.featureToToggle.putAll(features); } - public boolean isEndWithEol() { - return endWithEol; - } - - public void setEndWithEol(boolean endWithEol) { - this.endWithEol = endWithEol; - } - public boolean isSpaceBeforeSeparator() { return spaceBeforeSeparator; } @@ -68,4 +61,12 @@ public boolean isSpaceBeforeSeparator() { public void setSpaceBeforeSeparator(boolean spaceBeforeSeparator) { this.spaceBeforeSeparator = spaceBeforeSeparator; } + + // public int getIndentSpaces() { + // return indentSpaces; + // } + + // public void setIndentSpaces(int indentSpaces) { + // this.indentSpaces = indentSpaces; + // } } diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 05cc349e9f..d6326cba12 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -3,6 +3,11 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`). ## [Unreleased] +### Added +* Introduce `` ([#1492](https://github.com/diffplug/spotless/pull/1492)) + * **POTENTIALLY BREAKING** `JacksonYaml` is now configured with a `Map` to configure features +* Jackson (JSON and YAML) has new `spaceBeforeSeparator` option + * **POTENTIALLY BREAKING** `spaceBeforeSeparator` is defaulted to false while the formatter was behaving with `true` ## [2.30.0] - 2023-01-13 ### Added diff --git a/plugin-maven/README.md b/plugin-maven/README.md index 2a455dfe07..6dca992591 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -924,12 +924,12 @@ Uses Jackson and YAMLFactory to pretty print objects: ```xml 2.14.1 - - INDENT_OUTPUT - - - DEFAULT_HAS_NO_DISABLED_FEATURE - + + true + false + true|false + + false ``` diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java index cf03ef21ca..31c59a5f81 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java @@ -33,10 +33,10 @@ public class JacksonJson implements FormatterStepFactory { @Parameter - String version = JacksonJsonStep.defaultVersion(); + private String version = JacksonJsonStep.defaultVersion(); @Parameter - boolean endWithEol = new JacksonConfig().isEndWithEol(); + private boolean spaceBeforeSeparator = new JacksonConfig().isSpaceBeforeSeparator(); @Parameter private Map features = Collections.emptyMap(); @@ -45,10 +45,8 @@ public class JacksonJson implements FormatterStepFactory { public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { JacksonConfig jacksonConfig = new JacksonConfig(); - if (features != null) { - jacksonConfig.appendFeatureToToggle(features); - } - jacksonConfig.setEndWithEol(endWithEol); + jacksonConfig.appendFeatureToToggle(features); + jacksonConfig.setSpaceBeforeSeparator(spaceBeforeSeparator); return JacksonJsonStep .create(jacksonConfig, version, stepConfig.getProvisioner()); diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java index d3d9558cf4..1cd23bc3cc 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java @@ -18,12 +18,11 @@ import java.util.Collections; import java.util.Map; -import com.diffplug.spotless.maven.FormatterFactory; - import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.json.JacksonConfig; +import com.diffplug.spotless.maven.FormatterFactory; import com.diffplug.spotless.maven.FormatterStepConfig; import com.diffplug.spotless.maven.FormatterStepFactory; import com.diffplug.spotless.yaml.JacksonYamlStep; @@ -37,10 +36,7 @@ public class JacksonYaml implements FormatterStepFactory { private String version = JacksonYamlStep.defaultVersion(); @Parameter - boolean endWithEol = new JacksonConfig().isEndWithEol(); - - @Parameter - boolean spaceBeforeSeparator = new JacksonConfig().isSpaceBeforeSeparator(); + private boolean spaceBeforeSeparator = new JacksonConfig().isSpaceBeforeSeparator(); @Parameter private Map features = Collections.emptyMap(); @@ -49,10 +45,8 @@ public class JacksonYaml implements FormatterStepFactory { public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { JacksonConfig jacksonConfig = new JacksonConfig(); - if (features != null) { - jacksonConfig.appendFeatureToToggle(features); - } - jacksonConfig.setEndWithEol(endWithEol); + jacksonConfig.appendFeatureToToggle(features); + jacksonConfig.setSpaceBeforeSeparator(spaceBeforeSeparator); return JacksonYamlStep .create(jacksonConfig, version, stepConfig.getProvisioner()); diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java index 90b3a2f077..b263be8173 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java @@ -70,7 +70,6 @@ public void testFormatJson_WithGson_defaultConfig_nestedObject() throws Exceptio assertFile("json_test.json").sameAsResource("json/nestedObjectAfter.json"); } - @Test public void testFormatJson_WithJackson_sortByKeys() throws Exception { writePomWithJsonSteps("true"); @@ -78,17 +77,17 @@ public void testFormatJson_WithJackson_sortByKeys() throws Exception { setFile("json_test.json").toResource("json/sortByKeysBefore.json"); String output = mavenRunner().withArguments("spotless:apply").runNoError().output(); - assertFile("json_test.json").sameAsResource("json/sortByKeysAfter.json"); + assertFile("json_test.json").sameAsResource("json/sortByKeysAfter_Jackson.json"); } @Test - public void testFormatJson_WithJackson_sortByKeys_noSpaceAfterKeySeparator() throws Exception { - writePomWithJsonSteps("falsetrue"); + public void testFormatJson_WithJackson_sortByKeys_spaceAfterKeySeparator() throws Exception { + writePomWithJsonSteps("truetrue"); setFile("json_test.json").toResource("json/sortByKeysBefore.json"); String output = mavenRunner().withArguments("spotless:apply").runNoError().output(); - assertFile("json_test.json").sameAsResource("json/sortByKeysAfter.json"); + assertFile("json_test.json").sameAsResource("json/sortByKeysAfter_Jackson_spaceAfterKeySeparator.json"); } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java index 15366ab2e6..51f8da99f4 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java @@ -49,12 +49,12 @@ public void testFormatYaml_WithJackson_defaultConfig_arrayBrackets() throws Exce } @Test - public void testFormatYaml_WithJackson_defaultConfig_arrayBrackets_withEol() throws Exception { - writePomWithYamlSteps("true"); + public void testFormatYaml_WithJackson_defaultConfig_arrayBrackets_spaceBeforeSeparator() throws Exception { + writePomWithYamlSteps("true"); setFile("yaml_test.yaml").toResource("yaml/array_with_bracket.yaml"); mavenRunner().withArguments("spotless:apply").runNoError(); - assertFile("yaml_test.yaml").sameAsResource("yaml/array_with_bracket.clean_with_eol.yaml"); + assertFile("yaml_test.yaml").sameAsResource("yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml"); } @Test diff --git a/testlib/src/main/resources/json/sortByKeysAfter_Jackson_noSpaceAfterKeySeparator.json b/testlib/src/main/resources/json/sortByKeysAfter_Jackson_spaceAfterKeySeparator.json similarity index 100% rename from testlib/src/main/resources/json/sortByKeysAfter_Jackson_noSpaceAfterKeySeparator.json rename to testlib/src/main/resources/json/sortByKeysAfter_Jackson_spaceAfterKeySeparator.json diff --git a/testlib/src/main/resources/yaml/array_with_bracket.clean_with_eol.yaml b/testlib/src/main/resources/yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml similarity index 81% rename from testlib/src/main/resources/yaml/array_with_bracket.clean_with_eol.yaml rename to testlib/src/main/resources/yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml index 6080ba1f82..c6f891b9de 100644 --- a/testlib/src/main/resources/yaml/array_with_bracket.clean_with_eol.yaml +++ b/testlib/src/main/resources/yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml @@ -9,4 +9,4 @@ episodes: - 7 best-jedi: name: "Obi-Wan" - side: "light" + side: "light" \ No newline at end of file From f05126de68661f584b1b5865b406f35f700ae2e5 Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Mon, 16 Jan 2023 23:02:39 +0400 Subject: [PATCH 03/48] Fix Spotbugs --- .../glue/json/JacksonJsonFormatterFunc.java | 79 ++++++++----------- .../diffplug/spotless/json/JacksonConfig.java | 1 + 2 files changed, 35 insertions(+), 45 deletions(-) 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 c19e56b549..c44ff0d0d1 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 @@ -16,8 +16,6 @@ package com.diffplug.spotless.glue.json; import java.io.IOException; -import java.util.stream.Collectors; -import java.util.stream.IntStream; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonProcessingException; @@ -79,25 +77,7 @@ protected DefaultPrettyPrinter makePrinter() { boolean spaceBeforeSeparator = jacksonConfig.isSpaceBeforeSeparator(); DefaultPrettyPrinter.Indenter indenter = new DefaultIndenter(getIndentation(), "\n"); - DefaultPrettyPrinter printer = new DefaultPrettyPrinter() { - private static final long serialVersionUID = 1L; - - @Override - public DefaultPrettyPrinter createInstance() { - return new DefaultPrettyPrinter(this); - } - - @Override - public DefaultPrettyPrinter withSeparators(Separators separators) { - this._separators = separators; - if (spaceBeforeSeparator) { - this._objectFieldValueSeparatorWithSpaces = " " + separators.getObjectFieldValueSeparator() + " "; - } else { - this._objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " "; - } - return this; - } - }; + DefaultPrettyPrinter printer = new SpotlessDefaultPrettyPrinter(spaceBeforeSeparator); printer.indentObjectsWith(indenter); printer.indentArraysWith(indenter); @@ -105,34 +85,18 @@ public DefaultPrettyPrinter withSeparators(Separators separators) { } protected String getIndentation() { - int indentSpaces = jacksonConfig.getIndentSpaces(); - if (indentSpaces < 0) { - return "\t"; - } else { - return IntStream.range(0, indentSpaces).mapToObj(i -> "").collect(Collectors.joining()); - } + // DefaultIndenter default constructor relies on this + return " "; + // int indentSpaces = jacksonConfig.getIndentSpaces(); + // if (indentSpaces < 0) { + // return "\t"; + // } else { + // return IntStream.range(0, indentSpaces).mapToObj(i -> "").collect(Collectors.joining()); + // } } protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { - // We may consider adding manually an initial '---' prefix to help management of multiple documents - // if (!input.trim().startsWith("---")) { - // input = "---" + "\n" + input; - // } - 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 - // 2023-01: For now, we get 'Cannot deserialize value of type `com.fasterxml.jackson.databind.node.ObjectNode` from Array value' - // JsonParser yamlParser = objectMapper.getFactory().createParser(input); - // List docs = objectMapper.readValues(yamlParser, ObjectNode.class).readAll(); - // return objectMapper.writeValueAsString(docs); - - // 2023-01: This returns JSON instead of YAML - // This will transit with a JsonNode - // A JsonNode may keep the comments from the input node - // JsonNode jsonNode = objectMapper.readTree(input); - //Not 'toPrettyString' as one could require no INDENT_OUTPUT - // return jsonNode.toPrettyString(); ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class); String outputFromjackson = objectMapper.writeValueAsString(objectNode); @@ -141,4 +105,29 @@ protected String format(ObjectMapper objectMapper, String input) throws IllegalA throw new AssertionError("Unable to format. input='" + input + "'", e); } } + + protected static class SpotlessDefaultPrettyPrinter extends DefaultPrettyPrinter { + private static final long serialVersionUID = 1L; + private final boolean spaceBeforeSeparator; + + public SpotlessDefaultPrettyPrinter(boolean spaceBeforeSeparator) { + this.spaceBeforeSeparator = spaceBeforeSeparator; + } + + @Override + public DefaultPrettyPrinter createInstance() { + return new DefaultPrettyPrinter(this); + } + + @Override + public DefaultPrettyPrinter withSeparators(Separators separators) { + this._separators = separators; + if (spaceBeforeSeparator) { + this._objectFieldValueSeparatorWithSpaces = " " + separators.getObjectFieldValueSeparator() + " "; + } else { + this._objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " "; + } + return this; + } + } } diff --git a/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java index 2fadf31d78..7690913f34 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java @@ -24,6 +24,7 @@ * A DTO holding the options for Jackson-based formatters */ public class JacksonConfig implements Serializable { + private static final long serialVersionUID = 1L; private static final Map DEFAULT_FEATURE_TOGGLES; From 5e1a0e97ed6d53f3694309e161460b459e9cb802 Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Tue, 17 Jan 2023 12:53:20 +0400 Subject: [PATCH 04/48] Jackson - Yaml starts diverging to enable yanml-specific options --- .../glue/json/JacksonJsonFormatterFunc.java | 40 ++++----- .../glue/yaml/JacksonYamlFormatterFunc.java | 28 +++++-- .../diffplug/spotless/json/JacksonConfig.java | 11 +-- .../spotless/yaml/JacksonYamlConfig.java | 50 ++++++++++++ .../spotless/yaml/JacksonYamlStep.java | 2 +- .../gradle/spotless/JacksonGradleConfig.java | 67 +++++++++++++++ .../gradle/spotless/JsonExtension.java | 23 +++++- .../gradle/spotless/SpotlessExtension.java | 6 ++ .../gradle/spotless/YamlExtension.java | 75 +++++++++++++++++ .../gradle/spotless/YamlExtensionTest.java | 81 +++++++++++++++++++ plugin-maven/CHANGES.md | 7 +- plugin-maven/README.md | 28 +++++-- .../spotless/maven/yaml/JacksonYaml.java | 10 ++- .../spotless/maven/yaml/YamlTest.java | 8 +- ...ay_with_bracket.clean.no_start_marker.yaml | 11 +++ ...th_bracket.clean.spaceBeforeSeparator.yaml | 6 +- 16 files changed, 387 insertions(+), 66 deletions(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlConfig.java create mode 100644 plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JacksonGradleConfig.java create mode 100644 plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java create mode 100644 plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java create mode 100644 testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.yaml 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 c44ff0d0d1..934e60fcd8 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 @@ -18,6 +18,7 @@ import java.io.IOException; import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonFactoryBuilder; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.util.DefaultIndenter; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; @@ -47,12 +48,22 @@ public String apply(String input) throws Exception { return format(objectMapper, input); } + protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { + try { + ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class); + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode); + } catch (JsonProcessingException e) { + throw new AssertionError("Unable to format. input='" + input + "'", e); + } + } + /** * @return a {@link JsonFactory}. May be overridden to handle alternative formats. * @see jackson-dataformats-text */ protected JsonFactory makeJsonFactory() { - return new JsonFactory(); + // We may later accept JsonFactory.Features + return new JsonFactoryBuilder().build(); } protected ObjectMapper makeObjectMapper() { @@ -76,7 +87,9 @@ protected ObjectMapper makeObjectMapper() { protected DefaultPrettyPrinter makePrinter() { boolean spaceBeforeSeparator = jacksonConfig.isSpaceBeforeSeparator(); - DefaultPrettyPrinter.Indenter indenter = new DefaultIndenter(getIndentation(), "\n"); + // DefaultIndenter default constructor relies on 2 whitespaces as default tabulation + // By we want to force '\n' as eol given Spotless provides LF-input (whatever the actual File content/current OS) + DefaultPrettyPrinter.Indenter indenter = new DefaultIndenter(" ", "\n"); DefaultPrettyPrinter printer = new SpotlessDefaultPrettyPrinter(spaceBeforeSeparator); printer.indentObjectsWith(indenter); @@ -84,28 +97,6 @@ protected DefaultPrettyPrinter makePrinter() { return printer; } - protected String getIndentation() { - // DefaultIndenter default constructor relies on this - return " "; - // int indentSpaces = jacksonConfig.getIndentSpaces(); - // if (indentSpaces < 0) { - // return "\t"; - // } else { - // return IntStream.range(0, indentSpaces).mapToObj(i -> "").collect(Collectors.joining()); - // } - } - - protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { - try { - ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class); - String outputFromjackson = objectMapper.writeValueAsString(objectNode); - - return outputFromjackson; - } catch (JsonProcessingException e) { - throw new AssertionError("Unable to format. input='" + input + "'", e); - } - } - protected static class SpotlessDefaultPrettyPrinter extends DefaultPrettyPrinter { private static final long serialVersionUID = 1L; private final boolean spaceBeforeSeparator; @@ -123,6 +114,7 @@ public DefaultPrettyPrinter createInstance() { public DefaultPrettyPrinter withSeparators(Separators separators) { this._separators = separators; if (spaceBeforeSeparator) { + // This is Jackson default behavior this._objectFieldValueSeparatorWithSpaces = " " + separators.getObjectFieldValueSeparator() + " "; } else { this._objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " "; 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 6a471a884c..f7686ad503 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 @@ -17,23 +17,39 @@ import java.io.IOException; +import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactoryBuilder; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import com.diffplug.spotless.glue.json.JacksonJsonFormatterFunc; -import com.diffplug.spotless.json.JacksonConfig; +import com.diffplug.spotless.yaml.JacksonYamlConfig; public class JacksonYamlFormatterFunc extends JacksonJsonFormatterFunc { + final JacksonYamlConfig yamlConfig; - public JacksonYamlFormatterFunc(JacksonConfig jacksonConfig) { + public JacksonYamlFormatterFunc(JacksonYamlConfig jacksonConfig) { super(jacksonConfig); + this.yamlConfig = jacksonConfig; } - @Override - protected YAMLFactory makeJsonFactory() { - return new YAMLFactory(); + protected JsonFactory makeJsonFactory() { + YAMLFactoryBuilder yamlFactoryBuilder = new YAMLFactoryBuilder(new YAMLFactory()); + + // Configure the ObjectMapper + // https://github.com/FasterXML/jackson-databind#commonly-used-features + yamlConfig.getYamlFeatureToToggle().forEach((rawFeature, toggle) -> { + // https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection + // Refers to 'com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature' + YAMLGenerator.Feature feature = YAMLGenerator.Feature.valueOf(rawFeature); + + yamlFactoryBuilder.configure(feature, toggle); + }); + + return yamlFactoryBuilder.build(); } @Override @@ -56,7 +72,7 @@ protected String format(ObjectMapper objectMapper, String input) throws IllegalA //Not 'toPrettyString' as one could require no INDENT_OUTPUT // return jsonNode.toPrettyString(); ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class); - String outputFromjackson = objectMapper.writeValueAsString(objectNode); + String outputFromjackson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode); return outputFromjackson; } catch (JsonProcessingException e) { diff --git a/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java index 7690913f34..d5ca990b08 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java @@ -31,6 +31,7 @@ public class JacksonConfig implements Serializable { static { Map defaultFeatureToggles = new LinkedHashMap<>(); // We activate by default the PrettyPrinter from Jackson + // @see com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT defaultFeatureToggles.put("INDENT_OUTPUT", true); DEFAULT_FEATURE_TOGGLES = defaultFeatureToggles; } @@ -41,8 +42,6 @@ public class JacksonConfig implements Serializable { // By default, Jackson adds a ' ' before separator, which is not standard with most IDE/JSON libraries protected boolean spaceBeforeSeparator = false; - // protected int indentSpaces; - public Map getFeatureToToggle() { return Collections.unmodifiableMap(featureToToggle); } @@ -62,12 +61,4 @@ public boolean isSpaceBeforeSeparator() { public void setSpaceBeforeSeparator(boolean spaceBeforeSeparator) { this.spaceBeforeSeparator = spaceBeforeSeparator; } - - // public int getIndentSpaces() { - // return indentSpaces; - // } - - // public void setIndentSpaces(int indentSpaces) { - // this.indentSpaces = indentSpaces; - // } } diff --git a/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlConfig.java b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlConfig.java new file mode 100644 index 0000000000..2463b9603f --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlConfig.java @@ -0,0 +1,50 @@ +/* + * 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.yaml; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.diffplug.spotless.json.JacksonConfig; + +/** + * Specialization of {@link JacksonConfig} for YAML documents + */ +public class JacksonYamlConfig extends JacksonConfig { + private static final long serialVersionUID = 1L; + + protected Map yamlFeatureToToggle = new LinkedHashMap<>(); + + public Map getYamlFeatureToToggle() { + return Collections.unmodifiableMap(yamlFeatureToToggle); + } + + /** + * @see com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature + */ + public void setYamlFeatureToToggle(Map yamlFeatureToToggle) { + this.yamlFeatureToToggle = yamlFeatureToToggle; + } + + /** + * @see com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature + */ + public void appendYamlFeatureToToggle(Map features) { + this.yamlFeatureToToggle.putAll(features); + } + +} diff --git a/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java index 944d90e6c9..04d1895fe0 100644 --- a/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java +++ b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java @@ -73,7 +73,7 @@ private State(JacksonConfig jacksonConfig, FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.yaml.JacksonYamlFormatterFunc"); - Constructor constructor = formatterFunc.getConstructor(JacksonConfig.class); + Constructor constructor = formatterFunc.getConstructor(JacksonYamlConfig.class); return (FormatterFunc) constructor.newInstance(jacksonConfig); } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JacksonGradleConfig.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JacksonGradleConfig.java new file mode 100644 index 0000000000..d388f45d11 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JacksonGradleConfig.java @@ -0,0 +1,67 @@ +/* + * 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.gradle.spotless; + +import java.util.Collections; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.json.JacksonConfig; +import com.diffplug.spotless.json.JacksonJsonStep; + +public abstract class JacksonGradleConfig { + protected final FormatExtension formatExtension; + + protected JacksonConfig jacksonConfig; + + protected String version = JacksonJsonStep.defaultVersion(); + + public JacksonGradleConfig(JacksonConfig jacksonConfig, FormatExtension formatExtension) { + this.formatExtension = formatExtension; + + this.jacksonConfig = jacksonConfig; + formatExtension.addStep(createStep()); + } + + public JacksonGradleConfig(FormatExtension formatExtension) { + this(new JacksonConfig(), formatExtension); + } + + public JacksonGradleConfig config(JacksonConfig jacksonConfig) { + this.jacksonConfig = jacksonConfig; + formatExtension.replaceStep(createStep()); + return this; + } + + public JacksonGradleConfig feature(String feature, boolean toggle) { + this.jacksonConfig.appendFeatureToToggle(Collections.singletonMap(feature, toggle)); + formatExtension.replaceStep(createStep()); + return this; + } + + public JacksonGradleConfig spaceBeforeSeparator(boolean toggle) { + this.jacksonConfig.setSpaceBeforeSeparator(toggle); + formatExtension.replaceStep(createStep()); + return this; + } + + public JacksonGradleConfig version(String version) { + this.version = version; + formatExtension.replaceStep(createStep()); + return this; + } + + protected abstract FormatterStep createStep(); +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java index 25182fa4bc..542b2d5431 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import javax.inject.Inject; import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.json.JacksonConfig; +import com.diffplug.spotless.json.JacksonJsonStep; import com.diffplug.spotless.json.JsonSimpleStep; import com.diffplug.spotless.json.gson.GsonStep; @@ -47,6 +49,10 @@ public GsonConfig gson() { return new GsonConfig(); } + public JacksonJsonGradleConfig jackson() { + return new JacksonJsonGradleConfig(this); + } + public class SimpleConfig { private int indent; @@ -108,4 +114,19 @@ private FormatterStep createStep() { } } + public static class JacksonJsonGradleConfig extends JacksonGradleConfig { + + public JacksonJsonGradleConfig(JacksonConfig jacksonConfig, FormatExtension formatExtension) { + super(jacksonConfig, formatExtension); + } + + public JacksonJsonGradleConfig(FormatExtension formatExtension) { + this(new JacksonConfig(), formatExtension); + } + + @Override + protected FormatterStep createStep() { + return JacksonJsonStep.create(jacksonConfig, version, formatExtension.provisioner()); + } + } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java index 1c65fad310..8c3128e02d 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java @@ -193,6 +193,12 @@ public void json(Action closure) { format(JsonExtension.NAME, JsonExtension.class, closure); } + /** Configures the special YAML-specific extension. */ + public void yaml(Action closure) { + requireNonNull(closure); + format(JsonExtension.NAME, YamlExtension.class, closure); + } + /** Configures a custom extension. */ public void format(String name, Action closure) { requireNonNull(name, "name"); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java new file mode 100644 index 0000000000..5907ea985a --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java @@ -0,0 +1,75 @@ +/* + * 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. + * 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.util.Collections; + +import javax.inject.Inject; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.json.JacksonJsonStep; +import com.diffplug.spotless.yaml.JacksonYamlConfig; +import com.diffplug.spotless.yaml.JacksonYamlStep; + +public class YamlExtension extends FormatExtension { + private static final String DEFAULT_GSON_VERSION = JacksonJsonStep.defaultVersion(); + static final String NAME = "yaml"; + + @Inject + public YamlExtension(SpotlessExtension spotless) { + super(spotless); + } + + @Override + protected void setupTask(SpotlessTask task) { + if (target == null) { + throw noDefaultTargetException(); + } + super.setupTask(task); + } + + public JacksonGradleConfig jackson() { + return new JacksonYamlGradleConfig(this); + } + + public class JacksonYamlGradleConfig extends JacksonGradleConfig { + protected JacksonYamlConfig jacksonConfig; + + public JacksonYamlGradleConfig(JacksonYamlConfig jacksonConfig, FormatExtension formatExtension) { + super(jacksonConfig, formatExtension); + + this.jacksonConfig = jacksonConfig; + } + + public JacksonYamlGradleConfig(FormatExtension formatExtension) { + this(new JacksonYamlConfig(), formatExtension); + } + + /** + * @see com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature + */ + public JacksonGradleConfig yamlFeature(String feature, boolean toggle) { + this.jacksonConfig.appendYamlFeatureToToggle(Collections.singletonMap(feature, toggle)); + formatExtension.replaceStep(createStep()); + return this; + } + + @Override + protected FormatterStep createStep() { + return JacksonYamlStep.create(jacksonConfig, version, provisioner()); + } + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java new file mode 100644 index 0000000000..c16053a661 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java @@ -0,0 +1,81 @@ +/* + * 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.gradle.spotless; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +class YamlExtensionTest extends GradleIntegrationHarness { + @Test + void testFormatYaml_WithJackson_defaultConfig_separatorComments() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " yaml {", + " target 'src/**/*.yaml'", + " jackson()", + "}", + "}"); + setFile("src/main/resources/example.yaml").toResource("yaml/separator_comments.yaml"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("src/main/resources/example.yaml").sameAsResource("yaml/separator_comments.clean.yaml"); + } + + @Test + void testFormatYaml_WithJackson_defaultConfig_arrayBrackets_spaceBeforeSeparator() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " yaml {", + " target 'src/**/*.yaml'", + " jackson().spaceBeforeSeparator(true)", + "}", + "}"); + setFile("src/main/resources/example.yaml").toResource("yaml/array_with_bracket.yaml"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("src/main/resources/example.yaml").sameAsResource("yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml"); + } + + // see YAMLGenerator.Feature.WRITE_DOC_START_MARKER + @Test + void testFormatYaml_WithJackson_skipDocStartMarker() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " yaml {", + " target 'src/**/*.yaml'", + " jackson().yamlFeature('WRITE_DOC_START_MARKER', false)", + "}", + "}"); + setFile("src/main/resources/example.yaml").toResource("yaml/array_with_bracket.yaml"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("src/main/resources/example.yaml").sameAsResource("yaml/array_with_bracket.clean.no_start_marker.yaml"); + } + +} diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index d6326cba12..5bcc4b24c6 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -3,13 +3,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`). ## [Unreleased] -### Added -* Introduce `` ([#1492](https://github.com/diffplug/spotless/pull/1492)) - * **POTENTIALLY BREAKING** `JacksonYaml` is now configured with a `Map` to configure features -* Jackson (JSON and YAML) has new `spaceBeforeSeparator` option - * **POTENTIALLY BREAKING** `spaceBeforeSeparator` is defaulted to false while the formatter was behaving with `true` -## [2.30.0] - 2023-01-13 +# [2.30.0] - 2023-01-13 ### Added * Add option `editorConfigFile` for `ktLint` [#142](https://github.com/diffplug/spotless/issues/142) * **POTENTIALLY BREAKING** `ktlint` step now modifies license headers. Make sure to put `licenseHeader` *after* `ktlint`. diff --git a/plugin-maven/README.md b/plugin-maven/README.md index 6dca992591..e76d424611 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -12,8 +12,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) -[![Javadoc](https://img.shields.io/badge/javadoc-yes-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-maven-plugin/2.30.0/index.html) -[![Changelog](https://img.shields.io/badge/changelog-2.30.0-brightgreen.svg)](CHANGES.md) +[![Javadoc](https://img.shields.io/badge/javadoc-yes-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-maven-plugin/2.29.0/index.html) +[![Changelog](https://img.shields.io/badge/changelog-2.29.0-brightgreen.svg)](CHANGES.md) [![Circle CI](https://circleci.com/gh/diffplug/spotless/tree/main.svg?style=shield)](https://circleci.com/gh/diffplug/spotless/tree/main) [![Live chat](https://img.shields.io/badge/gitter-chat-brightgreen.svg)](https://gitter.im/diffplug/spotless) @@ -864,6 +864,7 @@ For details, see the [npm detection](#npm-detection) and [`.npmrc` detection](#n + ``` @@ -898,6 +899,22 @@ all HTML characters are written escaped or none. Set `escapeHtml` if you prefer [javadoc of String](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#compareTo(java.lang.String)) for details. +### Jackson + +Uses Jackson for formatting. + +```xml + + 2.14.1 + + true + false + true|false + + false + +``` + @@ -924,10 +941,11 @@ Uses Jackson and YAMLFactory to pretty print objects: ```xml 2.14.1 - - true + + true false - true|false + false + true|false false diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java index 1cd23bc3cc..9741c3f5d0 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java @@ -21,10 +21,10 @@ import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.json.JacksonConfig; import com.diffplug.spotless.maven.FormatterFactory; import com.diffplug.spotless.maven.FormatterStepConfig; import com.diffplug.spotless.maven.FormatterStepFactory; +import com.diffplug.spotless.yaml.JacksonYamlConfig; import com.diffplug.spotless.yaml.JacksonYamlStep; /** @@ -36,16 +36,20 @@ public class JacksonYaml implements FormatterStepFactory { private String version = JacksonYamlStep.defaultVersion(); @Parameter - private boolean spaceBeforeSeparator = new JacksonConfig().isSpaceBeforeSeparator(); + private boolean spaceBeforeSeparator = new JacksonYamlConfig().isSpaceBeforeSeparator(); @Parameter private Map features = Collections.emptyMap(); + @Parameter + private Map yamlFeatures = Collections.emptyMap(); + @Override public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { - JacksonConfig jacksonConfig = new JacksonConfig(); + JacksonYamlConfig jacksonConfig = new JacksonYamlConfig(); jacksonConfig.appendFeatureToToggle(features); + jacksonConfig.appendYamlFeatureToToggle(yamlFeatures); jacksonConfig.setSpaceBeforeSeparator(spaceBeforeSeparator); return JacksonYamlStep diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java index 51f8da99f4..aaea2c65b1 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java @@ -15,14 +15,11 @@ */ package com.diffplug.spotless.maven.yaml; -import static org.assertj.core.api.Assertions.assertThat; - import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.diffplug.spotless.maven.MavenIntegrationHarness; -import com.diffplug.spotless.maven.MavenRunner.Result; public class YamlTest extends MavenIntegrationHarness { private static final Logger LOGGER = LoggerFactory.getLogger(YamlTest.class); @@ -32,10 +29,7 @@ public void testFormatYaml_WithJackson_defaultConfig_separatorComments() throws writePomWithYamlSteps(""); setFile("yaml_test.yaml").toResource("yaml/separator_comments.yaml"); - Result runNoError = mavenRunner().withArguments("spotless:apply").runNoError(); - LOGGER.error("result: {}", runNoError); - assertThat(runNoError.exitValue()).as("Run without error %s", runNoError).isEqualTo(0); - LOGGER.error("GOGO"); + mavenRunner().withArguments("spotless:apply").runNoError(); assertFile("yaml_test.yaml").sameAsResource("yaml/separator_comments.clean.yaml"); } diff --git a/testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.yaml b/testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.yaml new file mode 100644 index 0000000000..7f2f5a96cd --- /dev/null +++ b/testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.yaml @@ -0,0 +1,11 @@ +episodes: +- 1 +- 2 +- 3 +- 4 +- 5 +- 6 +- 7 +best-jedi: + name: "Obi-Wan" + side: "light" diff --git a/testlib/src/main/resources/yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml b/testlib/src/main/resources/yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml index c6f891b9de..7dc9255e06 100644 --- a/testlib/src/main/resources/yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml +++ b/testlib/src/main/resources/yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml @@ -7,6 +7,6 @@ episodes: - 5 - 6 - 7 -best-jedi: - name: "Obi-Wan" - side: "light" \ No newline at end of file +best-jedi : + name : "Obi-Wan" + side : "light" From 533dc9999c56ca8dbd381f3ea74b79bcd5774e50 Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 00:33:53 +0400 Subject: [PATCH 05/48] Progress with gradle and tests --- .../glue/json/AJacksonFormatterFunc.java | 87 +++++++++++++++++++ .../glue/json/JacksonJsonFormatterFunc.java | 59 ++++--------- .../glue/yaml/JacksonYamlFormatterFunc.java | 9 +- .../diffplug/spotless/json/JacksonConfig.java | 14 +-- .../spotless/json/JacksonJsonConfig.java | 59 +++++++++++++ .../spotless/json/JacksonJsonStep.java | 6 +- .../spotless/yaml/JacksonYamlStep.java | 13 +-- .../gradle/spotless/JacksonGradleConfig.java | 6 -- .../gradle/spotless/JsonExtension.java | 28 +++++- .../gradle/spotless/YamlExtension.java | 6 +- .../gradle/spotless/JsonExtensionTest.java | 56 ++++++++---- .../gradle/spotless/YamlExtensionTest.java | 33 ++----- .../spotless/maven/json/JacksonJson.java | 6 +- .../spotless/maven/yaml/JacksonYaml.java | 4 - ...ay_with_bracket.clean.no_start_marker.yaml | 4 +- 15 files changed, 259 insertions(+), 131 deletions(-) create mode 100644 lib/src/jackson/java/com/diffplug/spotless/glue/json/AJacksonFormatterFunc.java create mode 100644 lib/src/main/java/com/diffplug/spotless/json/JacksonJsonConfig.java 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 new file mode 100644 index 0000000000..a835c85f9a --- /dev/null +++ b/lib/src/jackson/java/com/diffplug/spotless/glue/json/AJacksonFormatterFunc.java @@ -0,0 +1,87 @@ +/* + * 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.glue.json; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.json.JacksonConfig; + +/** + * A {@link FormatterFunc} based on Jackson library + */ +// https://github.com/FasterXML/jackson-dataformats-text/issues/372 +public abstract class AJacksonFormatterFunc implements FormatterFunc { + private JacksonConfig jacksonConfig; + + public AJacksonFormatterFunc(JacksonConfig jacksonConfig) { + this.jacksonConfig = jacksonConfig; + } + + @Override + public String apply(String input) throws Exception { + ObjectMapper objectMapper = makeObjectMapper(); + + return format(objectMapper, input); + } + + protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { + try { + ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class); + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode); + } catch (JsonProcessingException e) { + throw new AssertionError("Unable to format. input='" + input + "'", e); + } + } + + /** + * @return a {@link JsonFactory}. May be overridden to handle alternative formats. + * @see jackson-dataformats-text + */ + protected abstract JsonFactory makeJsonFactory(); + + protected ObjectMapper makeObjectMapper() { + JsonFactory jsonFactory = makeJsonFactory(); + ObjectMapper objectMapper = new ObjectMapper(jsonFactory); + + objectMapper.setDefaultPrettyPrinter(makePrinter()); + + // Configure the ObjectMapper + // https://github.com/FasterXML/jackson-databind#commonly-used-features + jacksonConfig.getFeatureToToggle().forEach((rawFeature, toggle) -> { + // https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection + SerializationFeature feature = SerializationFeature.valueOf(rawFeature); + + objectMapper.configure(feature, toggle); + }); + + new JsonFactory().configure(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT, true); + + return objectMapper; + } + + protected DefaultPrettyPrinter makePrinter() { + return new DefaultPrettyPrinter(); + } +} 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 934e60fcd8..5aadcc568f 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,99 +15,72 @@ */ package com.diffplug.spotless.glue.json; -import java.io.IOException; - import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonFactoryBuilder; -import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.util.DefaultIndenter; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.core.util.Separators; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.diffplug.spotless.FormatterFunc; -import com.diffplug.spotless.json.JacksonConfig; +import com.diffplug.spotless.json.JacksonJsonConfig; /** * A {@link FormatterFunc} based on Jackson library */ // https://github.com/FasterXML/jackson-dataformats-text/issues/372 -public class JacksonJsonFormatterFunc implements FormatterFunc { - private JacksonConfig jacksonConfig; +public class JacksonJsonFormatterFunc extends AJacksonFormatterFunc { + private JacksonJsonConfig jacksonConfig; - public JacksonJsonFormatterFunc(JacksonConfig jacksonConfig) { + public JacksonJsonFormatterFunc(JacksonJsonConfig jacksonConfig) { + super(jacksonConfig); this.jacksonConfig = jacksonConfig; } - @Override - public String apply(String input) throws Exception { - ObjectMapper objectMapper = makeObjectMapper(); - - return format(objectMapper, input); - } - - protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { - try { - ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class); - return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode); - } catch (JsonProcessingException e) { - throw new AssertionError("Unable to format. input='" + input + "'", e); - } - } - /** * @return a {@link JsonFactory}. May be overridden to handle alternative formats. * @see jackson-dataformats-text */ protected JsonFactory makeJsonFactory() { - // We may later accept JsonFactory.Features - return new JsonFactoryBuilder().build(); - } - - protected ObjectMapper makeObjectMapper() { - JsonFactory jsonFactory = makeJsonFactory(); - ObjectMapper objectMapper = new ObjectMapper(jsonFactory); - - objectMapper.setDefaultPrettyPrinter(makePrinter()); + JsonFactory jsonFactory = new JsonFactoryBuilder().build(); // Configure the ObjectMapper // https://github.com/FasterXML/jackson-databind#commonly-used-features - jacksonConfig.getFeatureToToggle().forEach((rawFeature, toggle) -> { + jacksonConfig.getJsonFeatureToToggle().forEach((rawFeature, toggle) -> { // https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection - SerializationFeature feature = SerializationFeature.valueOf(rawFeature); + JsonGenerator.Feature feature = JsonGenerator.Feature.valueOf(rawFeature); - objectMapper.configure(feature, toggle); + jsonFactory.configure(feature, toggle); }); - return objectMapper; + return jsonFactory; } + @Override protected DefaultPrettyPrinter makePrinter() { boolean spaceBeforeSeparator = jacksonConfig.isSpaceBeforeSeparator(); // DefaultIndenter default constructor relies on 2 whitespaces as default tabulation // By we want to force '\n' as eol given Spotless provides LF-input (whatever the actual File content/current OS) DefaultPrettyPrinter.Indenter indenter = new DefaultIndenter(" ", "\n"); - DefaultPrettyPrinter printer = new SpotlessDefaultPrettyPrinter(spaceBeforeSeparator); + DefaultPrettyPrinter printer = new SpotlessJsonPrettyPrinter(spaceBeforeSeparator); printer.indentObjectsWith(indenter); printer.indentArraysWith(indenter); return printer; } - protected static class SpotlessDefaultPrettyPrinter extends DefaultPrettyPrinter { + protected static class SpotlessJsonPrettyPrinter extends DefaultPrettyPrinter { private static final long serialVersionUID = 1L; private final boolean spaceBeforeSeparator; - public SpotlessDefaultPrettyPrinter(boolean spaceBeforeSeparator) { + public SpotlessJsonPrettyPrinter(boolean spaceBeforeSeparator) { this.spaceBeforeSeparator = spaceBeforeSeparator; } @Override public DefaultPrettyPrinter createInstance() { - return new DefaultPrettyPrinter(this); + return new SpotlessJsonPrettyPrinter(spaceBeforeSeparator); } @Override 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 f7686ad503..3986374c9e 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 @@ -25,15 +25,19 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactoryBuilder; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; -import com.diffplug.spotless.glue.json.JacksonJsonFormatterFunc; +import com.diffplug.spotless.glue.json.AJacksonFormatterFunc; import com.diffplug.spotless.yaml.JacksonYamlConfig; -public class JacksonYamlFormatterFunc extends JacksonJsonFormatterFunc { +public class JacksonYamlFormatterFunc extends AJacksonFormatterFunc { final JacksonYamlConfig yamlConfig; public JacksonYamlFormatterFunc(JacksonYamlConfig jacksonConfig) { super(jacksonConfig); this.yamlConfig = jacksonConfig; + + if (jacksonConfig == null) { + throw new IllegalArgumentException("ARG"); + } } protected JsonFactory makeJsonFactory() { @@ -43,7 +47,6 @@ protected JsonFactory makeJsonFactory() { // https://github.com/FasterXML/jackson-databind#commonly-used-features yamlConfig.getYamlFeatureToToggle().forEach((rawFeature, toggle) -> { // https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection - // Refers to 'com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature' YAMLGenerator.Feature feature = YAMLGenerator.Feature.valueOf(rawFeature); yamlFactoryBuilder.configure(feature, toggle); diff --git a/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java index d5ca990b08..250b77d1f1 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java @@ -21,7 +21,7 @@ import java.util.Map; /** - * A DTO holding the options for Jackson-based formatters + * A DTO holding the basic for Jackson-based formatters */ public class JacksonConfig implements Serializable { private static final long serialVersionUID = 1L; @@ -38,10 +38,6 @@ public class JacksonConfig implements Serializable { protected Map featureToToggle = new LinkedHashMap<>(); - // https://github.com/revelc/formatter-maven-plugin/pull/280 - // By default, Jackson adds a ' ' before separator, which is not standard with most IDE/JSON libraries - protected boolean spaceBeforeSeparator = false; - public Map getFeatureToToggle() { return Collections.unmodifiableMap(featureToToggle); } @@ -53,12 +49,4 @@ public void setFeatureToToggle(Map featureToToggle) { public void appendFeatureToToggle(Map features) { this.featureToToggle.putAll(features); } - - public boolean isSpaceBeforeSeparator() { - return spaceBeforeSeparator; - } - - public void setSpaceBeforeSeparator(boolean spaceBeforeSeparator) { - this.spaceBeforeSeparator = spaceBeforeSeparator; - } } diff --git a/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonConfig.java b/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonConfig.java new file mode 100644 index 0000000000..50b28605d1 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonConfig.java @@ -0,0 +1,59 @@ +/* + * 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.json; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Specialization of {@link JacksonConfig} for JSON documents + */ +public class JacksonJsonConfig extends JacksonConfig { + private static final long serialVersionUID = 1L; + + protected Map jsonFeatureToToggle = new LinkedHashMap<>(); + + // https://github.com/revelc/formatter-maven-plugin/pull/280 + // By default, Jackson adds a ' ' before separator, which is not standard with most IDE/JSON libraries + protected boolean spaceBeforeSeparator = false; + + public Map getJsonFeatureToToggle() { + return Collections.unmodifiableMap(jsonFeatureToToggle); + } + + /** + * @see com.fasterxml.jackson.core.JsonGenerator.Feature + */ + public void setJsonFeatureToToggle(Map jsonFeatureToToggle) { + this.jsonFeatureToToggle = jsonFeatureToToggle; + } + + /** + * @see com.fasterxml.jackson.core.JsonGenerator.Feature + */ + public void appendJsonFeatureToToggle(Map features) { + this.jsonFeatureToToggle.putAll(features); + } + + public boolean isSpaceBeforeSeparator() { + return spaceBeforeSeparator; + } + + public void setSpaceBeforeSeparator(boolean spaceBeforeSeparator) { + this.spaceBeforeSeparator = spaceBeforeSeparator; + } +} 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 0fe1ab057b..f15edbbd42 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java @@ -41,7 +41,7 @@ public static String defaultVersion() { return DEFAULT_VERSION; } - public static FormatterStep create(JacksonConfig jacksonConfig, + public static FormatterStep create(JacksonJsonConfig jacksonConfig, String jacksonVersion, Provisioner provisioner) { Objects.requireNonNull(provisioner, "provisioner cannot be null"); @@ -51,7 +51,7 @@ public static FormatterStep create(JacksonConfig jacksonConfig, } public static FormatterStep create(Provisioner provisioner) { - return create(new JacksonConfig(), defaultVersion(), provisioner); + return create(new JacksonJsonConfig(), defaultVersion(), provisioner); } private static final class State implements Serializable { @@ -72,7 +72,7 @@ private State(JacksonConfig jacksonConfig, FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.json.JacksonJsonFormatterFunc"); - Constructor constructor = formatterFunc.getConstructor(JacksonConfig.class); + Constructor constructor = formatterFunc.getConstructor(JacksonJsonConfig.class); return (FormatterFunc) constructor.newInstance(jacksonConfig); } } diff --git a/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java index 04d1895fe0..201199fe8b 100644 --- a/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java +++ b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java @@ -25,7 +25,6 @@ import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.JarState; import com.diffplug.spotless.Provisioner; -import com.diffplug.spotless.json.JacksonConfig; /** * Simple YAML formatter which reformats the file according to Jackson YAMLFactory. @@ -42,7 +41,7 @@ public static String defaultVersion() { return DEFAULT_VERSION; } - public static FormatterStep create(JacksonConfig jacksonConfig, + public static FormatterStep create(JacksonYamlConfig jacksonConfig, String jacksonVersion, Provisioner provisioner) { Objects.requireNonNull(provisioner, "provisioner cannot be null"); @@ -52,21 +51,25 @@ public static FormatterStep create(JacksonConfig jacksonConfig, } public static FormatterStep create(Provisioner provisioner) { - return create(new JacksonConfig(), defaultVersion(), provisioner); + return create(new JacksonYamlConfig(), defaultVersion(), provisioner); } private static final class State implements Serializable { private static final long serialVersionUID = 1L; - private final JacksonConfig jacksonConfig; + private final JacksonYamlConfig jacksonConfig; private final JarState jarState; - private State(JacksonConfig jacksonConfig, + private State(JacksonYamlConfig jacksonConfig, String jacksonVersion, Provisioner provisioner) throws IOException { this.jacksonConfig = jacksonConfig; + if (jacksonConfig == null) { + throw new IllegalArgumentException("ARG"); + } + this.jarState = JarState.from(JacksonYamlStep.MAVEN_COORDINATE + jacksonVersion, provisioner); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JacksonGradleConfig.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JacksonGradleConfig.java index d388f45d11..afc0c36371 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JacksonGradleConfig.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JacksonGradleConfig.java @@ -51,12 +51,6 @@ public JacksonGradleConfig feature(String feature, boolean toggle) { return this; } - public JacksonGradleConfig spaceBeforeSeparator(boolean toggle) { - this.jacksonConfig.setSpaceBeforeSeparator(toggle); - formatExtension.replaceStep(createStep()); - return this; - } - public JacksonGradleConfig version(String version) { this.version = version; formatExtension.replaceStep(createStep()); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java index 542b2d5431..31f605809c 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java @@ -15,10 +15,12 @@ */ package com.diffplug.gradle.spotless; +import java.util.Collections; + import javax.inject.Inject; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.json.JacksonConfig; +import com.diffplug.spotless.json.JacksonJsonConfig; import com.diffplug.spotless.json.JacksonJsonStep; import com.diffplug.spotless.json.JsonSimpleStep; import com.diffplug.spotless.json.gson.GsonStep; @@ -49,7 +51,7 @@ public GsonConfig gson() { return new GsonConfig(); } - public JacksonJsonGradleConfig jackson() { + public JacksonJsonGradleConfig jacksonJson() { return new JacksonJsonGradleConfig(this); } @@ -115,17 +117,35 @@ private FormatterStep createStep() { } public static class JacksonJsonGradleConfig extends JacksonGradleConfig { + protected JacksonJsonConfig jacksonConfig; - public JacksonJsonGradleConfig(JacksonConfig jacksonConfig, FormatExtension formatExtension) { + public JacksonJsonGradleConfig(JacksonJsonConfig jacksonConfig, FormatExtension formatExtension) { super(jacksonConfig, formatExtension); + this.jacksonConfig = jacksonConfig; + + if (jacksonConfig == null) { + throw new IllegalArgumentException("ARG2"); + } } public JacksonJsonGradleConfig(FormatExtension formatExtension) { - this(new JacksonConfig(), formatExtension); + this(new JacksonJsonConfig(), formatExtension); + } + + /** + * @see com.fasterxml.jackson.core.JsonGenerator.Feature + */ + public JacksonGradleConfig jsonFeature(String feature, boolean toggle) { + this.jacksonConfig.appendJsonFeatureToToggle(Collections.singletonMap(feature, toggle)); + formatExtension.replaceStep(createStep()); + return this; } @Override protected FormatterStep createStep() { + if (jacksonConfig == null) { + throw new IllegalArgumentException("ARG3"); + } return JacksonJsonStep.create(jacksonConfig, version, formatExtension.provisioner()); } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java index 5907ea985a..3fc12e0495 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java @@ -41,7 +41,7 @@ protected void setupTask(SpotlessTask task) { super.setupTask(task); } - public JacksonGradleConfig jackson() { + public JacksonGradleConfig jacksonYaml() { return new JacksonYamlGradleConfig(this); } @@ -52,6 +52,10 @@ public JacksonYamlGradleConfig(JacksonYamlConfig jacksonConfig, FormatExtension super(jacksonConfig, formatExtension); this.jacksonConfig = jacksonConfig; + + if (jacksonConfig == null) { + throw new IllegalArgumentException("ARG"); + } } public JacksonYamlGradleConfig(FormatExtension formatExtension) { diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JsonExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JsonExtensionTest.java index e2fb9f70aa..945c72cf75 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JsonExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JsonExtensionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,9 +30,9 @@ void simpleDefaultFormatting() throws IOException { "repositories { mavenCentral() }", "spotless {", " json {", - " target 'examples/**/*.json'", - " simple()", - "}", + " target 'examples/**/*.json'", + " simple()", + " }", "}"); setFile("src/main/resources/example.json").toResource("json/nestedObjectBefore.json"); setFile("examples/main/resources/example.json").toResource("json/nestedObjectBefore.json"); @@ -51,9 +51,9 @@ void simpleFormattingWithCustomNumberOfSpaces() throws IOException { "repositories { mavenCentral() }", "spotless {", " json {", - " target 'src/**/*.json'", - " simple().indentWithSpaces(6)", - "}", + " target 'src/**/*.json'", + " simple().indentWithSpaces(6)", + " }", "}"); setFile("src/main/resources/example.json").toResource("json/singletonArrayBefore.json"); gradleRunner().withArguments("spotlessApply").build(); @@ -70,9 +70,9 @@ void gsonDefaultFormatting() throws IOException { "repositories { mavenCentral() }", "spotless {", " json {", - " target 'examples/**/*.json'", - " gson()", - "}", + " target 'examples/**/*.json'", + " gson()", + " }", "}"); setFile("src/main/resources/example.json").toResource("json/nestedObjectBefore.json"); setFile("examples/main/resources/example.json").toResource("json/nestedObjectBefore.json"); @@ -91,9 +91,9 @@ void gsonFormattingWithCustomNumberOfSpaces() throws IOException { "repositories { mavenCentral() }", "spotless {", " json {", - " target 'src/**/*.json'", - " gson().indentWithSpaces(6)", - "}", + " target 'src/**/*.json'", + " gson().indentWithSpaces(6)", + " }", "}"); setFile("src/main/resources/example.json").toResource("json/singletonArrayBefore.json"); gradleRunner().withArguments("spotlessApply").build(); @@ -110,9 +110,9 @@ void gsonFormattingWithSortingByKeys() throws IOException { "repositories { mavenCentral() }", "spotless {", " json {", - " target 'src/**/*.json'", - " gson().sortByKeys()", - "}", + " target 'src/**/*.json'", + " gson().sortByKeys()", + " }", "}"); setFile("src/main/resources/example.json").toResource("json/sortByKeysBefore.json"); gradleRunner().withArguments("spotlessApply").build(); @@ -129,13 +129,31 @@ void gsonFormattingWithHtmlEscape() throws IOException { "repositories { mavenCentral() }", "spotless {", " json {", - " target 'src/**/*.json'", - " gson().escapeHtml()", - "}", + " target 'src/**/*.json'", + " gson().escapeHtml()", + " }", "}"); setFile("src/main/resources/example.json").toResource("json/escapeHtmlGsonBefore.json"); gradleRunner().withArguments("spotlessApply").build(); assertFile("src/main/resources/example.json").sameAsResource("json/escapeHtmlGsonAfter.json"); } + @Test + void jacksonFormattingWithSortingByKeys() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " json {", + " target 'src/**/*.json'", + " jacksonJson().feature('ORDER_MAP_ENTRIES_BY_KEYS', true)", + " }", + "}"); + setFile("src/main/resources/example.json").toResource("json/sortByKeysBefore.json"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("src/main/resources/example.json").sameAsResource("json/sortByKeysAfter_Jackson.json"); + } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java index c16053a661..79e0df0d8d 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java @@ -30,34 +30,15 @@ void testFormatYaml_WithJackson_defaultConfig_separatorComments() throws IOExcep "repositories { mavenCentral() }", "spotless {", " yaml {", - " target 'src/**/*.yaml'", - " jackson()", - "}", + " target 'src/**/*.yaml'", + " jacksonYaml()", + " }", "}"); setFile("src/main/resources/example.yaml").toResource("yaml/separator_comments.yaml"); gradleRunner().withArguments("spotlessApply").build(); assertFile("src/main/resources/example.yaml").sameAsResource("yaml/separator_comments.clean.yaml"); } - @Test - void testFormatYaml_WithJackson_defaultConfig_arrayBrackets_spaceBeforeSeparator() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'java'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " yaml {", - " target 'src/**/*.yaml'", - " jackson().spaceBeforeSeparator(true)", - "}", - "}"); - setFile("src/main/resources/example.yaml").toResource("yaml/array_with_bracket.yaml"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/resources/example.yaml").sameAsResource("yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml"); - } - // see YAMLGenerator.Feature.WRITE_DOC_START_MARKER @Test void testFormatYaml_WithJackson_skipDocStartMarker() throws IOException { @@ -69,9 +50,11 @@ void testFormatYaml_WithJackson_skipDocStartMarker() throws IOException { "repositories { mavenCentral() }", "spotless {", " yaml {", - " target 'src/**/*.yaml'", - " jackson().yamlFeature('WRITE_DOC_START_MARKER', false)", - "}", + " target 'src/**/*.yaml'", + " jacksonYaml()" + + " .yamlFeature('WRITE_DOC_START_MARKER', false)" + + " .yamlFeature('MINIMIZE_QUOTES', true)", + " }", "}"); setFile("src/main/resources/example.yaml").toResource("yaml/array_with_bracket.yaml"); gradleRunner().withArguments("spotlessApply").build(); diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java index 31c59a5f81..9bca852754 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java @@ -21,7 +21,7 @@ import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.json.JacksonConfig; +import com.diffplug.spotless.json.JacksonJsonConfig; import com.diffplug.spotless.json.JacksonJsonStep; import com.diffplug.spotless.maven.FormatterFactory; import com.diffplug.spotless.maven.FormatterStepConfig; @@ -36,14 +36,14 @@ public class JacksonJson implements FormatterStepFactory { private String version = JacksonJsonStep.defaultVersion(); @Parameter - private boolean spaceBeforeSeparator = new JacksonConfig().isSpaceBeforeSeparator(); + private boolean spaceBeforeSeparator = new JacksonJsonConfig().isSpaceBeforeSeparator(); @Parameter private Map features = Collections.emptyMap(); @Override public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { - JacksonConfig jacksonConfig = new JacksonConfig(); + JacksonJsonConfig jacksonConfig = new JacksonJsonConfig(); jacksonConfig.appendFeatureToToggle(features); jacksonConfig.setSpaceBeforeSeparator(spaceBeforeSeparator); diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java index 9741c3f5d0..d97cc41647 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java @@ -35,9 +35,6 @@ public class JacksonYaml implements FormatterStepFactory { @Parameter private String version = JacksonYamlStep.defaultVersion(); - @Parameter - private boolean spaceBeforeSeparator = new JacksonYamlConfig().isSpaceBeforeSeparator(); - @Parameter private Map features = Collections.emptyMap(); @@ -50,7 +47,6 @@ public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { jacksonConfig.appendFeatureToToggle(features); jacksonConfig.appendYamlFeatureToToggle(yamlFeatures); - jacksonConfig.setSpaceBeforeSeparator(spaceBeforeSeparator); return JacksonYamlStep .create(jacksonConfig, version, stepConfig.getProvisioner()); diff --git a/testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.yaml b/testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.yaml index 7f2f5a96cd..0e5571531a 100644 --- a/testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.yaml +++ b/testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.yaml @@ -7,5 +7,5 @@ episodes: - 6 - 7 best-jedi: - name: "Obi-Wan" - side: "light" + name: Obi-Wan + side: light From 4d6f9610e6542dd91029c9031d2a93c5c89319bb Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 00:50:12 +0400 Subject: [PATCH 06/48] Small fixes --- ...leConfig.java => AJacksonGradleConfig.java} | 18 ++++-------------- .../gradle/spotless/JsonExtension.java | 4 ++-- .../gradle/spotless/YamlExtension.java | 6 +++--- .../gradle/spotless/YamlExtensionTest.java | 4 ++-- 4 files changed, 11 insertions(+), 21 deletions(-) rename plugin-gradle/src/main/java/com/diffplug/gradle/spotless/{JacksonGradleConfig.java => AJacksonGradleConfig.java} (71%) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JacksonGradleConfig.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/AJacksonGradleConfig.java similarity index 71% rename from plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JacksonGradleConfig.java rename to plugin-gradle/src/main/java/com/diffplug/gradle/spotless/AJacksonGradleConfig.java index afc0c36371..e3d5cee661 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JacksonGradleConfig.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/AJacksonGradleConfig.java @@ -21,37 +21,27 @@ import com.diffplug.spotless.json.JacksonConfig; import com.diffplug.spotless.json.JacksonJsonStep; -public abstract class JacksonGradleConfig { +public abstract class AJacksonGradleConfig { protected final FormatExtension formatExtension; protected JacksonConfig jacksonConfig; protected String version = JacksonJsonStep.defaultVersion(); - public JacksonGradleConfig(JacksonConfig jacksonConfig, FormatExtension formatExtension) { + public AJacksonGradleConfig(JacksonConfig jacksonConfig, FormatExtension formatExtension) { this.formatExtension = formatExtension; this.jacksonConfig = jacksonConfig; formatExtension.addStep(createStep()); } - public JacksonGradleConfig(FormatExtension formatExtension) { - this(new JacksonConfig(), formatExtension); - } - - public JacksonGradleConfig config(JacksonConfig jacksonConfig) { - this.jacksonConfig = jacksonConfig; - formatExtension.replaceStep(createStep()); - return this; - } - - public JacksonGradleConfig feature(String feature, boolean toggle) { + public AJacksonGradleConfig feature(String feature, boolean toggle) { this.jacksonConfig.appendFeatureToToggle(Collections.singletonMap(feature, toggle)); formatExtension.replaceStep(createStep()); return this; } - public JacksonGradleConfig version(String version) { + public AJacksonGradleConfig version(String version) { this.version = version; formatExtension.replaceStep(createStep()); return this; diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java index 31f605809c..94e94c639e 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java @@ -116,7 +116,7 @@ private FormatterStep createStep() { } } - public static class JacksonJsonGradleConfig extends JacksonGradleConfig { + public static class JacksonJsonGradleConfig extends AJacksonGradleConfig { protected JacksonJsonConfig jacksonConfig; public JacksonJsonGradleConfig(JacksonJsonConfig jacksonConfig, FormatExtension formatExtension) { @@ -135,7 +135,7 @@ public JacksonJsonGradleConfig(FormatExtension formatExtension) { /** * @see com.fasterxml.jackson.core.JsonGenerator.Feature */ - public JacksonGradleConfig jsonFeature(String feature, boolean toggle) { + public AJacksonGradleConfig jsonFeature(String feature, boolean toggle) { this.jacksonConfig.appendJsonFeatureToToggle(Collections.singletonMap(feature, toggle)); formatExtension.replaceStep(createStep()); return this; diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java index 3fc12e0495..e5dc3d79ac 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java @@ -41,11 +41,11 @@ protected void setupTask(SpotlessTask task) { super.setupTask(task); } - public JacksonGradleConfig jacksonYaml() { + public AJacksonGradleConfig jacksonYaml() { return new JacksonYamlGradleConfig(this); } - public class JacksonYamlGradleConfig extends JacksonGradleConfig { + public class JacksonYamlGradleConfig extends AJacksonGradleConfig { protected JacksonYamlConfig jacksonConfig; public JacksonYamlGradleConfig(JacksonYamlConfig jacksonConfig, FormatExtension formatExtension) { @@ -65,7 +65,7 @@ public JacksonYamlGradleConfig(FormatExtension formatExtension) { /** * @see com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature */ - public JacksonGradleConfig yamlFeature(String feature, boolean toggle) { + public AJacksonGradleConfig yamlFeature(String feature, boolean toggle) { this.jacksonConfig.appendYamlFeatureToToggle(Collections.singletonMap(feature, toggle)); formatExtension.replaceStep(createStep()); return this; diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java index 79e0df0d8d..618b8d17ca 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java @@ -51,8 +51,8 @@ void testFormatYaml_WithJackson_skipDocStartMarker() throws IOException { "spotless {", " yaml {", " target 'src/**/*.yaml'", - " jacksonYaml()" + - " .yamlFeature('WRITE_DOC_START_MARKER', false)" + + " jacksonYaml()" , + " .yamlFeature('WRITE_DOC_START_MARKER', false)" , " .yamlFeature('MINIMIZE_QUOTES', true)", " }", "}"); From 38bc206a73283d0ddff1d10bb1fb45b3493edff3 Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 00:53:57 +0400 Subject: [PATCH 07/48] Fix style --- .../java/com/diffplug/gradle/spotless/YamlExtensionTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java index 618b8d17ca..ee5ee33366 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java @@ -51,8 +51,8 @@ void testFormatYaml_WithJackson_skipDocStartMarker() throws IOException { "spotless {", " yaml {", " target 'src/**/*.yaml'", - " jacksonYaml()" , - " .yamlFeature('WRITE_DOC_START_MARKER', false)" , + " jacksonYaml()", + " .yamlFeature('WRITE_DOC_START_MARKER', false)", " .yamlFeature('MINIMIZE_QUOTES', true)", " }", "}"); From 949c0984671a3092219d0e7674d885bbe160f51a Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 09:35:44 +0400 Subject: [PATCH 08/48] Fix Yaml referring to Json --- .../com/diffplug/gradle/spotless/SpotlessExtension.java | 2 +- plugin-maven/CHANGES.md | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java index 8c3128e02d..0a31a15764 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java @@ -196,7 +196,7 @@ public void json(Action closure) { /** Configures the special YAML-specific extension. */ public void yaml(Action closure) { requireNonNull(closure); - format(JsonExtension.NAME, YamlExtension.class, closure); + format(YamlExtension.NAME, YamlExtension.class, closure); } /** Configures a custom extension. */ diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 97eea5d7c0..cba82652cc 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -4,12 +4,16 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added +* Introduce `` ([#1492](https://github.com/diffplug/spotless/pull/1492)) + * **POTENTIALLY BREAKING** `JacksonYaml` is now configured with a `Map` to configure features +* Jackson (`json` and `yaml`) has new `spaceBeforeSeparator` option + * **POTENTIALLY BREAKING** `spaceBeforeSeparator` is defaulted to false while the formatter was behaving with `true` ### Fixed * The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494) ### Changes * Spotless' custom build was replaced by [`maven-plugin-development`](https://github.com/britter/maven-plugin-development). ([#1496](https://github.com/diffplug/spotless/pull/1496) fixes [#554](https://github.com/diffplug/spotless/issues/554)) -# [2.30.0] - 2023-01-13 +## [2.30.0] - 2023-01-13 ### Added * Add option `editorConfigFile` for `ktLint` [#142](https://github.com/diffplug/spotless/issues/142) * **POTENTIALLY BREAKING** `ktlint` step now modifies license headers. Make sure to put `licenseHeader` *after* `ktlint`. From 37875ce62ef876c08a6357a191aa2abf80221f38 Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 10:28:10 +0400 Subject: [PATCH 09/48] Rename to jackson step for both yaml and json --- .../com/diffplug/spotless/yaml/JacksonYamlStep.java | 6 ++---- .../gradle/spotless/AJacksonGradleConfig.java | 2 +- .../com/diffplug/gradle/spotless/JsonExtension.java | 12 ++++-------- .../com/diffplug/gradle/spotless/YamlExtension.java | 10 +++++----- .../diffplug/gradle/spotless/JsonExtensionTest.java | 2 +- .../diffplug/gradle/spotless/YamlExtensionTest.java | 6 +++--- ...ith_bracket.clean.no_start_marker.no_quotes.yaml} | 0 7 files changed, 16 insertions(+), 22 deletions(-) rename testlib/src/main/resources/yaml/{array_with_bracket.clean.no_start_marker.yaml => array_with_bracket.clean.no_start_marker.no_quotes.yaml} (100%) diff --git a/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java index 201199fe8b..a87e40f420 100644 --- a/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java +++ b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java @@ -30,6 +30,7 @@ * Simple YAML formatter which reformats the file according to Jackson YAMLFactory. */ // https://stackoverflow.com/questions/14515994/convert-json-string-to-pretty-print-json-output-using-jackson +// https://stackoverflow.com/questions/60891174/i-want-to-load-a-yaml-file-possibly-edit-the-data-and-then-dump-it-again-how public class JacksonYamlStep { static final String MAVEN_COORDINATE = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:"; // https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml @@ -44,6 +45,7 @@ public static String defaultVersion() { public static FormatterStep create(JacksonYamlConfig jacksonConfig, String jacksonVersion, Provisioner provisioner) { + Objects.requireNonNull(jacksonConfig, "jacksonConfig cannot be null"); Objects.requireNonNull(provisioner, "provisioner cannot be null"); return FormatterStep.createLazy("yaml", () -> new State(jacksonConfig, jacksonVersion, provisioner), @@ -66,10 +68,6 @@ private State(JacksonYamlConfig jacksonConfig, Provisioner provisioner) throws IOException { this.jacksonConfig = jacksonConfig; - if (jacksonConfig == null) { - throw new IllegalArgumentException("ARG"); - } - this.jarState = JarState.from(JacksonYamlStep.MAVEN_COORDINATE + jacksonVersion, provisioner); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/AJacksonGradleConfig.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/AJacksonGradleConfig.java index e3d5cee661..c017d41d04 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/AJacksonGradleConfig.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/AJacksonGradleConfig.java @@ -28,11 +28,11 @@ public abstract class AJacksonGradleConfig { protected String version = JacksonJsonStep.defaultVersion(); + // Make sure to call 'formatExtension.addStep(createStep());' in the extented constructors public AJacksonGradleConfig(JacksonConfig jacksonConfig, FormatExtension formatExtension) { this.formatExtension = formatExtension; this.jacksonConfig = jacksonConfig; - formatExtension.addStep(createStep()); } public AJacksonGradleConfig feature(String feature, boolean toggle) { diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java index 94e94c639e..b964fcb54d 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java @@ -51,7 +51,7 @@ public GsonConfig gson() { return new GsonConfig(); } - public JacksonJsonGradleConfig jacksonJson() { + public JacksonJsonGradleConfig jackson() { return new JacksonJsonGradleConfig(this); } @@ -123,9 +123,7 @@ public JacksonJsonGradleConfig(JacksonJsonConfig jacksonConfig, FormatExtension super(jacksonConfig, formatExtension); this.jacksonConfig = jacksonConfig; - if (jacksonConfig == null) { - throw new IllegalArgumentException("ARG2"); - } + formatExtension.addStep(createStep()); } public JacksonJsonGradleConfig(FormatExtension formatExtension) { @@ -141,11 +139,9 @@ public AJacksonGradleConfig jsonFeature(String feature, boolean toggle) { return this; } + // 'final' as it is called in the constructor @Override - protected FormatterStep createStep() { - if (jacksonConfig == null) { - throw new IllegalArgumentException("ARG3"); - } + protected final FormatterStep createStep() { return JacksonJsonStep.create(jacksonConfig, version, formatExtension.provisioner()); } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java index e5dc3d79ac..26b742f02a 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java @@ -19,6 +19,7 @@ import javax.inject.Inject; +import com.diffplug.common.base.Throwables; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.json.JacksonJsonStep; import com.diffplug.spotless.yaml.JacksonYamlConfig; @@ -41,7 +42,7 @@ protected void setupTask(SpotlessTask task) { super.setupTask(task); } - public AJacksonGradleConfig jacksonYaml() { + public AJacksonGradleConfig jackson() { return new JacksonYamlGradleConfig(this); } @@ -53,9 +54,7 @@ public JacksonYamlGradleConfig(JacksonYamlConfig jacksonConfig, FormatExtension this.jacksonConfig = jacksonConfig; - if (jacksonConfig == null) { - throw new IllegalArgumentException("ARG"); - } + formatExtension.addStep(createStep()); } public JacksonYamlGradleConfig(FormatExtension formatExtension) { @@ -71,8 +70,9 @@ public AJacksonGradleConfig yamlFeature(String feature, boolean toggle) { return this; } + // 'final' as it is called in the constructor @Override - protected FormatterStep createStep() { + protected final FormatterStep createStep() { return JacksonYamlStep.create(jacksonConfig, version, provisioner()); } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JsonExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JsonExtensionTest.java index 945c72cf75..a878615eaa 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JsonExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JsonExtensionTest.java @@ -149,7 +149,7 @@ void jacksonFormattingWithSortingByKeys() throws IOException { "spotless {", " json {", " target 'src/**/*.json'", - " jacksonJson().feature('ORDER_MAP_ENTRIES_BY_KEYS', true)", + " jackson().feature('ORDER_MAP_ENTRIES_BY_KEYS', true)", " }", "}"); setFile("src/main/resources/example.json").toResource("json/sortByKeysBefore.json"); diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java index ee5ee33366..455dfc6dcf 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java @@ -31,7 +31,7 @@ void testFormatYaml_WithJackson_defaultConfig_separatorComments() throws IOExcep "spotless {", " yaml {", " target 'src/**/*.yaml'", - " jacksonYaml()", + " jackson()", " }", "}"); setFile("src/main/resources/example.yaml").toResource("yaml/separator_comments.yaml"); @@ -51,14 +51,14 @@ void testFormatYaml_WithJackson_skipDocStartMarker() throws IOException { "spotless {", " yaml {", " target 'src/**/*.yaml'", - " jacksonYaml()", + " jackson()", " .yamlFeature('WRITE_DOC_START_MARKER', false)", " .yamlFeature('MINIMIZE_QUOTES', true)", " }", "}"); setFile("src/main/resources/example.yaml").toResource("yaml/array_with_bracket.yaml"); gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/resources/example.yaml").sameAsResource("yaml/array_with_bracket.clean.no_start_marker.yaml"); + assertFile("src/main/resources/example.yaml").sameAsResource("yaml/array_with_bracket.clean.no_start_marker.no_quotes.yaml"); } } diff --git a/testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.yaml b/testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.no_quotes.yaml similarity index 100% rename from testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.yaml rename to testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.no_quotes.yaml From a22109e5e4b3e7daf4ae41b308d39bead18a06ef Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 10:41:53 +0400 Subject: [PATCH 10/48] spotlessApply --- .../spotless/glue/yaml/JacksonYamlFormatterFunc.java | 5 ----- .../java/com/diffplug/gradle/spotless/YamlExtension.java | 1 - .../java/com/diffplug/gradle/spotless/YamlExtensionTest.java | 2 +- plugin-maven/README.md | 4 ++-- .../test/java/com/diffplug/spotless/maven/yaml/YamlTest.java | 1 - 5 files changed, 3 insertions(+), 10 deletions(-) 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 3986374c9e..0cf4591717 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 @@ -57,11 +57,6 @@ protected JsonFactory makeJsonFactory() { @Override protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { - // We may consider adding manually an initial '---' prefix to help management of multiple documents - // if (!input.trim().startsWith("---")) { - // input = "---" + "\n" + input; - // } - 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 diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java index 26b742f02a..bf268914fc 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java @@ -19,7 +19,6 @@ import javax.inject.Inject; -import com.diffplug.common.base.Throwables; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.json.JacksonJsonStep; import com.diffplug.spotless.yaml.JacksonYamlConfig; diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java index 455dfc6dcf..7baddc4177 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java @@ -57,7 +57,7 @@ void testFormatYaml_WithJackson_skipDocStartMarker() throws IOException { " }", "}"); setFile("src/main/resources/example.yaml").toResource("yaml/array_with_bracket.yaml"); - gradleRunner().withArguments("spotlessApply").build(); + gradleRunner().withArguments("spotlessApply", "--stacktrace").build(); assertFile("src/main/resources/example.yaml").sameAsResource("yaml/array_with_bracket.clean.no_start_marker.no_quotes.yaml"); } diff --git a/plugin-maven/README.md b/plugin-maven/README.md index e76d424611..7673a440c6 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -12,8 +12,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) -[![Javadoc](https://img.shields.io/badge/javadoc-yes-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-maven-plugin/2.29.0/index.html) -[![Changelog](https://img.shields.io/badge/changelog-2.29.0-brightgreen.svg)](CHANGES.md) +[![Javadoc](https://img.shields.io/badge/javadoc-yes-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-maven-plugin/2.30.0/index.html) +[![Changelog](https://img.shields.io/badge/changelog-2.30.0-brightgreen.svg)](CHANGES.md) [![Circle CI](https://circleci.com/gh/diffplug/spotless/tree/main.svg?style=shield)](https://circleci.com/gh/diffplug/spotless/tree/main) [![Live chat](https://img.shields.io/badge/gitter-chat-brightgreen.svg)](https://gitter.im/diffplug/spotless) diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java index 084f1495eb..aaea2c65b1 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java @@ -19,7 +19,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.diffplug.spotless.ProcessRunner; import com.diffplug.spotless.maven.MavenIntegrationHarness; public class YamlTest extends MavenIntegrationHarness { From 7bc34f48328a42bdc26a58c2b1d22f2b68892256 Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 11:46:36 +0400 Subject: [PATCH 11/48] Switch from ObjectNode to Map to enable SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS --- .../glue/json/AJacksonFormatterFunc.java | 16 ++++++++------ .../glue/json/JacksonJsonFormatterFunc.java | 2 +- .../diffplug/spotless/json/JacksonConfig.java | 2 +- .../spotless/json/JacksonJsonConfig.java | 4 ++-- .../spotless/yaml/JacksonYamlConfig.java | 4 ++-- .../gradle/spotless/JsonExtension.java | 2 +- .../gradle/spotless/YamlExtension.java | 3 +-- .../spotless/maven/json/JsonTest.java | 2 +- .../spotless/maven/yaml/YamlTest.java | 9 -------- .../json/sortByKeysAfter_Jackson.json | 22 ++++++++----------- ...sAfter_Jackson_spaceAfterKeySeparator.json | 22 ++++++++----------- ...th_bracket.clean.spaceBeforeSeparator.yaml | 12 ---------- 12 files changed, 36 insertions(+), 64 deletions(-) delete mode 100644 testlib/src/main/resources/yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml 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 a835c85f9a..a41ecf90d2 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,10 +16,11 @@ 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.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.PrettyPrinter; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -48,8 +49,11 @@ public String apply(String input) throws Exception { protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { try { - ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class); - return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode); + // ObjectNode is not compatible with SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS + Map objectNode = objectMapper.readValue(input, Map.class); + String output = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode); + + return output; } catch (JsonProcessingException e) { throw new AssertionError("Unable to format. input='" + input + "'", e); } @@ -65,7 +69,7 @@ protected ObjectMapper makeObjectMapper() { JsonFactory jsonFactory = makeJsonFactory(); ObjectMapper objectMapper = new ObjectMapper(jsonFactory); - objectMapper.setDefaultPrettyPrinter(makePrinter()); + objectMapper.setDefaultPrettyPrinter(makePrettyPrinter()); // Configure the ObjectMapper // https://github.com/FasterXML/jackson-databind#commonly-used-features @@ -76,12 +80,10 @@ protected ObjectMapper makeObjectMapper() { objectMapper.configure(feature, toggle); }); - new JsonFactory().configure(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT, true); - return objectMapper; } - protected DefaultPrettyPrinter makePrinter() { + protected PrettyPrinter makePrettyPrinter() { return new DefaultPrettyPrinter(); } } 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 5aadcc568f..ae455fbbb4 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 @@ -57,7 +57,7 @@ protected JsonFactory makeJsonFactory() { } @Override - protected DefaultPrettyPrinter makePrinter() { + protected DefaultPrettyPrinter makePrettyPrinter() { boolean spaceBeforeSeparator = jacksonConfig.isSpaceBeforeSeparator(); // DefaultIndenter default constructor relies on 2 whitespaces as default tabulation diff --git a/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java index 250b77d1f1..f0dba8f072 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java @@ -36,7 +36,7 @@ public class JacksonConfig implements Serializable { DEFAULT_FEATURE_TOGGLES = defaultFeatureToggles; } - protected Map featureToToggle = new LinkedHashMap<>(); + protected Map featureToToggle = new LinkedHashMap<>(DEFAULT_FEATURE_TOGGLES); public Map getFeatureToToggle() { return Collections.unmodifiableMap(featureToToggle); diff --git a/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonConfig.java b/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonConfig.java index 50b28605d1..efff594663 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonConfig.java +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonConfig.java @@ -36,14 +36,14 @@ public Map getJsonFeatureToToggle() { } /** - * @see com.fasterxml.jackson.core.JsonGenerator.Feature + * Refers to com.fasterxml.jackson.core.JsonGenerator.Feature */ public void setJsonFeatureToToggle(Map jsonFeatureToToggle) { this.jsonFeatureToToggle = jsonFeatureToToggle; } /** - * @see com.fasterxml.jackson.core.JsonGenerator.Feature + * Refers to com.fasterxml.jackson.core.JsonGenerator.Feature */ public void appendJsonFeatureToToggle(Map features) { this.jsonFeatureToToggle.putAll(features); diff --git a/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlConfig.java b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlConfig.java index 2463b9603f..166ee5f307 100644 --- a/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlConfig.java +++ b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlConfig.java @@ -34,14 +34,14 @@ public Map getYamlFeatureToToggle() { } /** - * @see com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature + * Refers to com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature */ public void setYamlFeatureToToggle(Map yamlFeatureToToggle) { this.yamlFeatureToToggle = yamlFeatureToToggle; } /** - * @see com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature + * Refers to com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature */ public void appendYamlFeatureToToggle(Map features) { this.yamlFeatureToToggle.putAll(features); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java index b964fcb54d..ef0e993d37 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java @@ -131,7 +131,7 @@ public JacksonJsonGradleConfig(FormatExtension formatExtension) { } /** - * @see com.fasterxml.jackson.core.JsonGenerator.Feature + * @Refers to com.fasterxml.jackson.core.JsonGenerator.Feature */ public AJacksonGradleConfig jsonFeature(String feature, boolean toggle) { this.jacksonConfig.appendJsonFeatureToToggle(Collections.singletonMap(feature, toggle)); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java index bf268914fc..7be3264102 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java @@ -25,7 +25,6 @@ import com.diffplug.spotless.yaml.JacksonYamlStep; public class YamlExtension extends FormatExtension { - private static final String DEFAULT_GSON_VERSION = JacksonJsonStep.defaultVersion(); static final String NAME = "yaml"; @Inject @@ -61,7 +60,7 @@ public JacksonYamlGradleConfig(FormatExtension formatExtension) { } /** - * @see com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature + * Refers to com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature */ public AJacksonGradleConfig yamlFeature(String feature, boolean toggle) { this.jacksonConfig.appendYamlFeatureToToggle(Collections.singletonMap(feature, toggle)); diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java index 47de001a73..1897cc764a 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java @@ -76,7 +76,7 @@ public void testFormatJson_WithJackson_sortByKeys() throws Exception { setFile("json_test.json").toResource("json/sortByKeysBefore.json"); - mavenRunner().withArguments("spotless:apply").runNoError(); + mavenRunner().withArguments("spotless:apply", "-X").runNoError(); assertFile("json_test.json").sameAsResource("json/sortByKeysAfter_Jackson.json"); } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java index aaea2c65b1..6af4d09ce9 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java @@ -42,15 +42,6 @@ public void testFormatYaml_WithJackson_defaultConfig_arrayBrackets() throws Exce assertFile("yaml_test.yaml").sameAsResource("yaml/array_with_bracket.clean.yaml"); } - @Test - public void testFormatYaml_WithJackson_defaultConfig_arrayBrackets_spaceBeforeSeparator() throws Exception { - writePomWithYamlSteps("true"); - - setFile("yaml_test.yaml").toResource("yaml/array_with_bracket.yaml"); - mavenRunner().withArguments("spotless:apply").runNoError(); - assertFile("yaml_test.yaml").sameAsResource("yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml"); - } - @Test public void testFormatYaml_WithJackson_defaultConfig_multipleDocuments() throws Exception { writePomWithYamlSteps(""); diff --git a/testlib/src/main/resources/json/sortByKeysAfter_Jackson.json b/testlib/src/main/resources/json/sortByKeysAfter_Jackson.json index c4a48de2f2..3af39fd0fb 100644 --- a/testlib/src/main/resources/json/sortByKeysAfter_Jackson.json +++ b/testlib/src/main/resources/json/sortByKeysAfter_Jackson.json @@ -1,19 +1,15 @@ { + "A": 1, + "X": 2, + "_arraysNotSorted": [ 3, 2, 1 ], + "a": 3, + "c": 4, + "x": 5, + "z": { "A": 1, "X": 2, - "_arraysNotSorted": [ - 3, - 2, - 1 - ], "a": 3, "c": 4, - "x": 5, - "z": { - "A": 1, - "X": 2, - "a": 3, - "c": 4, - "x": 5 - } + "x": 5 + } } diff --git a/testlib/src/main/resources/json/sortByKeysAfter_Jackson_spaceAfterKeySeparator.json b/testlib/src/main/resources/json/sortByKeysAfter_Jackson_spaceAfterKeySeparator.json index c4a48de2f2..3af39fd0fb 100644 --- a/testlib/src/main/resources/json/sortByKeysAfter_Jackson_spaceAfterKeySeparator.json +++ b/testlib/src/main/resources/json/sortByKeysAfter_Jackson_spaceAfterKeySeparator.json @@ -1,19 +1,15 @@ { + "A": 1, + "X": 2, + "_arraysNotSorted": [ 3, 2, 1 ], + "a": 3, + "c": 4, + "x": 5, + "z": { "A": 1, "X": 2, - "_arraysNotSorted": [ - 3, - 2, - 1 - ], "a": 3, "c": 4, - "x": 5, - "z": { - "A": 1, - "X": 2, - "a": 3, - "c": 4, - "x": 5 - } + "x": 5 + } } diff --git a/testlib/src/main/resources/yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml b/testlib/src/main/resources/yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml deleted file mode 100644 index 7dc9255e06..0000000000 --- a/testlib/src/main/resources/yaml/array_with_bracket.clean.spaceBeforeSeparator.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -episodes: -- 1 -- 2 -- 3 -- 4 -- 5 -- 6 -- 7 -best-jedi : - name : "Obi-Wan" - side : "light" From 8d2cdd3d5c800161fa8149a8c4aa6f9d7d596849 Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 12:33:38 +0400 Subject: [PATCH 12/48] Fix management of multiple documents YAML --- .../glue/json/AJacksonFormatterFunc.java | 2 +- .../glue/yaml/JacksonYamlFormatterFunc.java | 31 +++++---- .../spotless/json/JsonSimpleStep.java | 6 +- .../gradle/spotless/YamlExtensionTest.java | 67 +++++++++++++++---- .../resources/yaml/array_at_root.clean.yaml | 5 ++ .../main/resources/yaml/array_at_root.yaml | 7 ++ .../yaml/multiple_documents.clean.yaml | 12 ++-- .../resources/yaml/multiple_documents.yaml | 14 ++-- 8 files changed, 99 insertions(+), 45 deletions(-) create mode 100644 testlib/src/main/resources/yaml/array_at_root.clean.yaml create mode 100644 testlib/src/main/resources/yaml/array_at_root.yaml 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 a41ecf90d2..244ce05a9e 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 @@ -55,7 +55,7 @@ protected String format(ObjectMapper objectMapper, String input) throws IllegalA return output; } catch (JsonProcessingException e) { - throw new AssertionError("Unable to format. input='" + input + "'", e); + throw new IllegalArgumentException("Unable to format. input='" + input + "'", e); } } 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 0cf4591717..96012e1041 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 @@ -15,12 +15,18 @@ */ package com.diffplug.spotless.glue.yaml; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.List; import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.SequenceWriter; +import com.fasterxml.jackson.databind.node.ContainerNode; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLFactoryBuilder; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; @@ -57,24 +63,21 @@ protected JsonFactory makeJsonFactory() { @Override protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { + //if (true) + // throw new IllegalArgumentException("input = \r\n" + input); 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 - // 2023-01: For now, we get 'Cannot deserialize value of type `com.fasterxml.jackson.databind.node.ObjectNode` from Array value' - // JsonParser yamlParser = objectMapper.getFactory().createParser(input); - // List docs = objectMapper.readValues(yamlParser, ObjectNode.class).readAll(); - // return objectMapper.writeValueAsString(docs); + JsonParser yamlParser = objectMapper.getFactory().createParser(input); + List documents = objectMapper.readValues(yamlParser, ContainerNode.class).readAll(); - // A JsonNode may keep the comments from the input node - // JsonNode jsonNode = objectMapper.readTree(input); - //Not 'toPrettyString' as one could require no INDENT_OUTPUT - // return jsonNode.toPrettyString(); - ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class); - String outputFromjackson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode); - - return outputFromjackson; + // https://github.com/FasterXML/jackson-dataformats-text/issues/66#issuecomment-554265055 + // https://github.com/FasterXML/jackson-dataformats-text/issues/66#issuecomment-554265055 + StringWriter stringWriter = new StringWriter(); + objectMapper.writer().writeValues(stringWriter).writeAll(documents).close(); + return stringWriter.toString(); } catch (JsonProcessingException e) { - throw new AssertionError("Unable to format. input='" + input + "'", e); + throw new IllegalArgumentException("Unable to format. input='" + input + "'", e); } } } diff --git a/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java b/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java index a970149133..1f4db3e059 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java +++ b/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java @@ -81,7 +81,7 @@ FormatterFunc toFormatter() { return format(arrayConstructor, arrayToString, s); } - throw new AssertionError(String.format("Unable to determine JSON type, expected a '{' or '[' but found '%s'", first)); + throw new IllegalArgumentException(String.format("Unable to determine JSON type, expected a '{' or '[' but found '%s'", first)); }; } @@ -89,8 +89,8 @@ private String format(Constructor constructor, Method toString, String input) try { Object parsed = constructor.newInstance(input); return toString.invoke(parsed, indentSpaces) + "\n"; - } catch (InvocationTargetException ex) { - throw new AssertionError("Unable to format JSON", ex.getCause()); + } catch (InvocationTargetException e) { + throw new IllegalArgumentException("Unable to format JSON", e); } } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java index 7baddc4177..295934dd26 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java @@ -43,22 +43,63 @@ void testFormatYaml_WithJackson_defaultConfig_separatorComments() throws IOExcep @Test void testFormatYaml_WithJackson_skipDocStartMarker() throws IOException { setFile("build.gradle").toLines( - "plugins {", - " id 'java'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " yaml {", - " target 'src/**/*.yaml'", - " jackson()", - " .yamlFeature('WRITE_DOC_START_MARKER', false)", - " .yamlFeature('MINIMIZE_QUOTES', true)", - " }", - "}"); + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " yaml {", + " target 'src/**/*.yaml'", + " jackson()", + " .yamlFeature('WRITE_DOC_START_MARKER', false)", + " .yamlFeature('MINIMIZE_QUOTES', true)", + " }", + "}"); setFile("src/main/resources/example.yaml").toResource("yaml/array_with_bracket.yaml"); gradleRunner().withArguments("spotlessApply", "--stacktrace").build(); assertFile("src/main/resources/example.yaml").sameAsResource("yaml/array_with_bracket.clean.no_start_marker.no_quotes.yaml"); } + @Test + void testFormatYaml_WithJackson_multipleDocuments() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " yaml {", + " target 'src/**/*.yaml'", + " jackson()", + " .yamlFeature('MINIMIZE_QUOTES', true)", + " }", + "}"); + setFile("src/main/resources/example.yaml").toResource("yaml/multiple_documents.yaml"); + gradleRunner().withArguments("spotlessApply", "--stacktrace").build(); + assertFile("src/main/resources/example.yaml").sameAsResource("yaml/multiple_documents.clean.jackson.yaml"); + } + + + @Test + void testFormatYaml_WithJackson_arrayAtRoot() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " yaml {", + " target 'src/**/*.yaml'", + " jackson()", + " }", + "}"); + setFile("src/main/resources/example.yaml").toResource("yaml/array_at_root.yaml"); + gradleRunner().withArguments("spotlessApply", "--stacktrace").build(); + assertFile("src/main/resources/example.yaml").sameAsResource("yaml/array_at_root.clean.yaml"); + } + + } diff --git a/testlib/src/main/resources/yaml/array_at_root.clean.yaml b/testlib/src/main/resources/yaml/array_at_root.clean.yaml new file mode 100644 index 0000000000..f9bae04114 --- /dev/null +++ b/testlib/src/main/resources/yaml/array_at_root.clean.yaml @@ -0,0 +1,5 @@ +--- +- key1: "value1" + key2: "value2" +- key3: "value3" + key4: "value4" diff --git a/testlib/src/main/resources/yaml/array_at_root.yaml b/testlib/src/main/resources/yaml/array_at_root.yaml new file mode 100644 index 0000000000..b26f6bb246 --- /dev/null +++ b/testlib/src/main/resources/yaml/array_at_root.yaml @@ -0,0 +1,7 @@ +- key1: value1 + key2: value2 + + +- key3: value3 + key4: value4 + diff --git a/testlib/src/main/resources/yaml/multiple_documents.clean.yaml b/testlib/src/main/resources/yaml/multiple_documents.clean.yaml index 45e0b0916b..3651473955 100644 --- a/testlib/src/main/resources/yaml/multiple_documents.clean.yaml +++ b/testlib/src/main/resources/yaml/multiple_documents.clean.yaml @@ -1,8 +1,8 @@ ---- -document: this is document 1 - ---- -document: this is document 2 +# Document 1 +key1: value1 +key2: value2 --- -document: this is document 3 \ No newline at end of file +# Document 2 +key3: value3 +key4: value4 diff --git a/testlib/src/main/resources/yaml/multiple_documents.yaml b/testlib/src/main/resources/yaml/multiple_documents.yaml index 51f14a5862..3651473955 100644 --- a/testlib/src/main/resources/yaml/multiple_documents.yaml +++ b/testlib/src/main/resources/yaml/multiple_documents.yaml @@ -1,10 +1,8 @@ -document: this is document 1 ---- - -document: this is document 2 - +# Document 1 +key1: value1 +key2: value2 --- - - -document: this is document 3 \ No newline at end of file +# Document 2 +key3: value3 +key4: value4 From a3693e78c1326a281dac76de9327515243d3f27e Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 12:36:06 +0400 Subject: [PATCH 13/48] spotlessApply --- .../glue/json/AJacksonFormatterFunc.java | 1 - .../glue/yaml/JacksonYamlFormatterFunc.java | 3 - .../spotless/json/JsonSimpleStep.java | 2 +- .../gradle/spotless/YamlExtension.java | 1 - .../gradle/spotless/YamlExtensionTest.java | 74 +++++++++---------- 5 files changed, 37 insertions(+), 44 deletions(-) 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 244ce05a9e..6f363ad1b7 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 @@ -24,7 +24,6 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.json.JacksonConfig; 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 96012e1041..edb350c559 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 @@ -15,17 +15,14 @@ */ package com.diffplug.spotless.glue.yaml; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringWriter; -import java.nio.charset.StandardCharsets; import java.util.List; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SequenceWriter; import com.fasterxml.jackson.databind.node.ContainerNode; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLFactoryBuilder; diff --git a/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java b/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java index 1f4db3e059..85ccbfa6e2 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java +++ b/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java index 7be3264102..abc2dce359 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java @@ -20,7 +20,6 @@ import javax.inject.Inject; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.json.JacksonJsonStep; import com.diffplug.spotless.yaml.JacksonYamlConfig; import com.diffplug.spotless.yaml.JacksonYamlStep; diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java index 295934dd26..82801eb0fd 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java @@ -43,19 +43,19 @@ void testFormatYaml_WithJackson_defaultConfig_separatorComments() throws IOExcep @Test void testFormatYaml_WithJackson_skipDocStartMarker() throws IOException { setFile("build.gradle").toLines( - "plugins {", - " id 'java'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " yaml {", - " target 'src/**/*.yaml'", - " jackson()", - " .yamlFeature('WRITE_DOC_START_MARKER', false)", - " .yamlFeature('MINIMIZE_QUOTES', true)", - " }", - "}"); + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " yaml {", + " target 'src/**/*.yaml'", + " jackson()", + " .yamlFeature('WRITE_DOC_START_MARKER', false)", + " .yamlFeature('MINIMIZE_QUOTES', true)", + " }", + "}"); setFile("src/main/resources/example.yaml").toResource("yaml/array_with_bracket.yaml"); gradleRunner().withArguments("spotlessApply", "--stacktrace").build(); assertFile("src/main/resources/example.yaml").sameAsResource("yaml/array_with_bracket.clean.no_start_marker.no_quotes.yaml"); @@ -64,42 +64,40 @@ void testFormatYaml_WithJackson_skipDocStartMarker() throws IOException { @Test void testFormatYaml_WithJackson_multipleDocuments() throws IOException { setFile("build.gradle").toLines( - "plugins {", - " id 'java'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " yaml {", - " target 'src/**/*.yaml'", - " jackson()", - " .yamlFeature('MINIMIZE_QUOTES', true)", - " }", - "}"); + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " yaml {", + " target 'src/**/*.yaml'", + " jackson()", + " .yamlFeature('MINIMIZE_QUOTES', true)", + " }", + "}"); setFile("src/main/resources/example.yaml").toResource("yaml/multiple_documents.yaml"); gradleRunner().withArguments("spotlessApply", "--stacktrace").build(); assertFile("src/main/resources/example.yaml").sameAsResource("yaml/multiple_documents.clean.jackson.yaml"); } - @Test void testFormatYaml_WithJackson_arrayAtRoot() throws IOException { setFile("build.gradle").toLines( - "plugins {", - " id 'java'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " yaml {", - " target 'src/**/*.yaml'", - " jackson()", - " }", - "}"); + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " yaml {", + " target 'src/**/*.yaml'", + " jackson()", + " }", + "}"); setFile("src/main/resources/example.yaml").toResource("yaml/array_at_root.yaml"); gradleRunner().withArguments("spotlessApply", "--stacktrace").build(); assertFile("src/main/resources/example.yaml").sameAsResource("yaml/array_at_root.clean.yaml"); } - } From c613302d0b1d4edece0c1d6a9a408aea716cedf4 Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 13:01:25 +0400 Subject: [PATCH 14/48] Improve documentation, fix tests --- plugin-gradle/CHANGES.md | 2 + plugin-gradle/README.md | 65 +++++++++++++++++-- plugin-maven/README.md | 20 ++++-- .../spotless/maven/json/JacksonJson.java | 4 ++ .../multiple_documents.clean.jackson.yaml | 6 +- 5 files changed, 83 insertions(+), 14 deletions(-) diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 63e5b43d74..94d7a90409 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -4,6 +4,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added +* Support `jackson()` for YAML files ([#1492](https://github.com/diffplug/spotless/pull/1492)) +* Support `jackson()` for JSON files ([#1492](https://github.com/diffplug/spotless/pull/1492)) ### Fixed * The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494) ### Changes diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index f2567a5924..e1bd2fe0dd 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -737,11 +737,12 @@ For details, see the [npm detection](#npm-detection) and [`.npmrc` detection](#n ```gradle spotless { json { - target 'src/**/*.json' // you have to set the target manually - simple() // has its own section below + target 'src/**/*.json' // you have to set the target manually + simple() // has its own section below prettier().config(['parser': 'json']) // see Prettier section below - eclipseWtp('json') // see Eclipse web tools platform section - gson() // has its own section below + eclipseWtp('json') // see Eclipse web tools platform section + gson() // has its own section below + jackson() // has its own section below } } ``` @@ -780,10 +781,60 @@ spotless { Notes: * There's no option in Gson to leave HTML as-is (i.e. escaped HTML would remain escaped, raw would remain raw). Either -all HTML characters are written escaped or none. Set `escapeHtml` if you prefer the former. + all HTML characters are written escaped or none. Set `escapeHtml` if you prefer the former. * `sortByKeys` will apply lexicographic order on the keys of the input JSON. See the -[javadoc of String](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#compareTo(java.lang.String)) -for details. + [javadoc of String](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#compareTo(java.lang.String)) + for details. + +### Jackson + +Uses Jackson for json files. + +```gradle +spotless { + json { + target 'src/**/*.json' + jackson() + .spaceBeforeSeparator(false) // optional: add a whitespace before key separator. False by default + .feature('INDENT_OUTPUT', true) // optional: true by default + .feature('ORDER_MAP_ENTRIES_BY_KEYS', true) // optional: false by default + .feature('ANY_OTHER_FEATURE', true|false) // optional: any SerializationFeature can be toggled on or off + .jsonFeature('ANY_OTHER_FEATURE', true|false) // any JsonGenerator.Feature can be toggled on or off + } +} +``` + + +## YAML + +- `com.diffplug.gradle.spotless.YamlExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.13.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 { + yaml { + target 'src/**/*.yaml' // you have to set the target manually + jackson() // has its own section below + } +} +``` + +### Jackson + +Uses Jackson for `yaml` files. + +```gradle +spotless { + yaml { + target 'src/**/*.yaml' + jackson() + .spaceBeforeSeparator(false) // optional: add a whitespace before key separator. False by default + .feature('INDENT_OUTPUT', true) // optional: true by default + .feature('ORDER_MAP_ENTRIES_BY_KEYS', true) // optional: false by default + .feature('ANY_OTHER_FEATURE', true|false) // optional: any SerializationFeature can be toggled on or off + .yamlFeature('ANY_OTHER_FEATURE', true|false) // any YAMLGenerator.Feature can be toggled on or off + } +} +``` diff --git a/plugin-maven/README.md b/plugin-maven/README.md index 7673a440c6..37d1046b1d 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -907,11 +907,15 @@ Uses Jackson for formatting. 2.14.1 - true - false - true|false + true + false + true|false - false + + false + true|false + + false ``` @@ -944,9 +948,13 @@ Uses Jackson and YAMLFactory to pretty print objects: true false - false - true|false + true|false + + true + false + true|false + false ``` diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java index 9bca852754..ecf925ddbe 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java @@ -41,11 +41,15 @@ public class JacksonJson implements FormatterStepFactory { @Parameter private Map features = Collections.emptyMap(); + @Parameter + private Map jsonFeatures = Collections.emptyMap(); + @Override public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { JacksonJsonConfig jacksonConfig = new JacksonJsonConfig(); jacksonConfig.appendFeatureToToggle(features); + jacksonConfig.appendJsonFeatureToToggle(jsonFeatures); jacksonConfig.setSpaceBeforeSeparator(spaceBeforeSeparator); return JacksonJsonStep diff --git a/testlib/src/main/resources/yaml/multiple_documents.clean.jackson.yaml b/testlib/src/main/resources/yaml/multiple_documents.clean.jackson.yaml index aa731919b4..ff56b4f77a 100644 --- a/testlib/src/main/resources/yaml/multiple_documents.clean.jackson.yaml +++ b/testlib/src/main/resources/yaml/multiple_documents.clean.jackson.yaml @@ -1,2 +1,6 @@ --- -document: "this is document 1" +key1: "value1" +key2: "value2" +--- +key3: "value3" +key4: "value4" From 97ecda34a74bb07e86fa3f4db38e70b9352f49ed Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 13:27:27 +0400 Subject: [PATCH 15/48] Fix Javadoc --- .../main/java/com/diffplug/gradle/spotless/JsonExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java index ef0e993d37..1e9de2e831 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java @@ -131,7 +131,7 @@ public JacksonJsonGradleConfig(FormatExtension formatExtension) { } /** - * @Refers to com.fasterxml.jackson.core.JsonGenerator.Feature + * Refers to com.fasterxml.jackson.core.JsonGenerator.Feature */ public AJacksonGradleConfig jsonFeature(String feature, boolean toggle) { this.jacksonConfig.appendJsonFeatureToToggle(Collections.singletonMap(feature, toggle)); From a80ed394092c9cdd76427f60221136db3795649f Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 13:51:01 +0400 Subject: [PATCH 16/48] Sync mvn and gradle tests on YAML --- .../java/com/diffplug/gradle/spotless/YamlExtensionTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java index 82801eb0fd..067c809846 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java @@ -73,7 +73,6 @@ void testFormatYaml_WithJackson_multipleDocuments() throws IOException { " yaml {", " target 'src/**/*.yaml'", " jackson()", - " .yamlFeature('MINIMIZE_QUOTES', true)", " }", "}"); setFile("src/main/resources/example.yaml").toResource("yaml/multiple_documents.yaml"); From 4b3cb2421996895eef8f871ac47a3089867780ae Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Mon, 16 Jan 2023 20:45:45 +0100 Subject: [PATCH 17/48] allow supplying node-binary path or look for it as a sibling to npm binary. the path of the npm binary is then prepended to the system PATH before launching the npm process. --- .../spotless/npm/EslintFormatterStep.java | 10 +-- .../spotless/npm/NodeExecutableResolver.java | 45 ++++++++++++++ .../npm/NpmFormatterStepLocations.java | 62 +++++++++++++++++++ .../npm/NpmFormatterStepStateBase.java | 18 ++---- .../spotless/npm/NpmPathResolver.java | 18 ++++-- .../com/diffplug/spotless/npm/NpmProcess.java | 16 +++-- .../spotless/npm/PrettierFormatterStep.java | 8 ++- .../spotless/npm/TsFmtFormatterStep.java | 8 ++- .../gradle/spotless/FormatExtension.java | 17 ++++- .../gradle/spotless/JavascriptExtension.java | 3 +- .../gradle/spotless/TypescriptExtension.java | 5 +- .../NpmTestsWithoutNpmInstallationTest.java | 59 ++++++++++++++++++ .../npm/AbstractNpmFormatterStepFactory.java | 11 +++- .../npm/NpmFormatterStepCommonTests.java | 9 ++- 14 files changed, 252 insertions(+), 37 deletions(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java create mode 100644 lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepLocations.java create mode 100644 plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java 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 81c5b6ce78..74979d90aa 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java @@ -94,9 +94,11 @@ private static class State extends NpmFormatterStepStateBase implements Serializ "/com/diffplug/spotless/npm/common-serve.js", "/com/diffplug/spotless/npm/eslint-serve.js"), npmPathResolver.resolveNpmrcContent()), - projectDir, - buildDir, - npmPathResolver.resolveNpmExecutable()); + new NpmFormatterStepLocations( + projectDir, + buildDir, + npmPathResolver.resolveNpmExecutable(), + npmPathResolver.resolveNodeExecutable())); this.eslintConfig = localCopyFiles(requireNonNull(eslintConfig)); } @@ -119,7 +121,7 @@ public FormatterFunc createFormatterFunc() { FormattedPrinter.SYSOUT.print("creating formatter function (starting server)"); ServerProcessInfo eslintRestServer = npmRunServer(); EslintRestService restService = new EslintRestService(eslintRestServer.getBaseUrl()); - return Closeable.ofDangerous(() -> endServer(restService, eslintRestServer), new EslintFilePathPassingFormatterFunc(projectDir, nodeModulesDir, eslintConfig, restService)); + return Closeable.ofDangerous(() -> endServer(restService, eslintRestServer), new EslintFilePathPassingFormatterFunc(locations.projectDir(), nodeModulesDir, eslintConfig, restService)); } catch (IOException e) { throw ThrowingEx.asRuntime(e); } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java new file mode 100644 index 0000000000..c9e04da620 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java @@ -0,0 +1,45 @@ +/* + * 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.npm; + +import java.io.File; +import java.util.Optional; + +class NodeExecutableResolver { + + private NodeExecutableResolver() { + // no instance + } + + static String nodeExecutableName() { + String nodeName = "node"; + if (PlatformInfo.normalizedOS() == PlatformInfo.OS.WINDOWS) { + nodeName += ".exe"; + } + return nodeName; + } + + static Optional tryFindNextTo(File npmExecutable) { + if (npmExecutable == null) { + return Optional.empty(); + } + File nodeExecutable = new File(npmExecutable.getParentFile(), nodeExecutableName()); + if (nodeExecutable.exists() && nodeExecutable.isFile() && nodeExecutable.canExecute()) { + return Optional.of(nodeExecutable); + } + return Optional.empty(); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepLocations.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepLocations.java new file mode 100644 index 0000000000..0c417733e8 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepLocations.java @@ -0,0 +1,62 @@ +/* + * 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.npm; + +import static java.util.Objects.requireNonNull; + +import java.io.File; +import java.io.Serializable; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +class NpmFormatterStepLocations implements Serializable { + + private static final long serialVersionUID = -1055408537924029969L; + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private final transient File projectDir; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private final transient File buildDir; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private final transient File npmExecutable; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private final transient File nodeExecutable; + + public NpmFormatterStepLocations(File projectDir, File buildDir, File npmExecutable, File nodeExecutable) { + this.projectDir = requireNonNull(projectDir); + this.buildDir = requireNonNull(buildDir); + this.npmExecutable = requireNonNull(npmExecutable); + this.nodeExecutable = requireNonNull(nodeExecutable); + } + + public File projectDir() { + return projectDir; + } + + public File buildDir() { + return buildDir; + } + + public File npmExecutable() { + return npmExecutable; + } + + public File nodeExecutable() { + return nodeExecutable; + } +} 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 49a166e45c..4f555a177e 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -48,23 +48,17 @@ abstract class NpmFormatterStepStateBase implements Serializable { @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") public final transient File nodeModulesDir; - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - private final transient File npmExecutable; - - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - public final transient File projectDir; + public final NpmFormatterStepLocations locations; private final NpmConfig npmConfig; private final String stepName; - protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, File projectDir, File buildDir, File npm) throws IOException { + protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, NpmFormatterStepLocations locations) throws IOException { this.stepName = requireNonNull(stepName); this.npmConfig = requireNonNull(npmConfig); - this.projectDir = requireNonNull(projectDir); - this.npmExecutable = npm; - - NodeServerLayout layout = prepareNodeServer(buildDir); + this.locations = locations; + NodeServerLayout layout = prepareNodeServer(locations.buildDir()); this.nodeModulesDir = layout.nodeModulesDir(); this.packageJsonSignature = FileSignature.signAsList(layout.packageJsonFile()); } @@ -88,7 +82,7 @@ private NodeServerLayout prepareNodeServer(File buildDir) throws IOException { } private void runNpmInstall(File npmProjectDir) throws IOException { - new NpmProcess(npmProjectDir, this.npmExecutable).install(); + new NpmProcess(npmProjectDir, this.locations.npmExecutable(), this.locations.nodeExecutable()).install(); } protected ServerProcessInfo npmRunServer() throws ServerStartException, IOException { @@ -102,7 +96,7 @@ protected ServerProcessInfo npmRunServer() throws ServerStartException, IOExcept final File serverPortFile = new File(this.nodeModulesDir, "server.port"); NpmResourceHelper.deleteFileIfExists(serverPortFile); // start the http server in node - Process server = new NpmProcess(this.nodeModulesDir, this.npmExecutable).start(); + Process server = new NpmProcess(this.nodeModulesDir, this.locations.npmExecutable(), this.locations.nodeExecutable()).start(); // await the readiness of the http server - wait for at most 60 seconds try { diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java index a9a15bd49d..d18146188d 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package com.diffplug.spotless.npm; import java.io.File; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -24,14 +25,17 @@ public class NpmPathResolver { private final File explicitNpmExecutable; + private final File explicitNodeExecutable; + private final File explicitNpmrcFile; private final List additionalNpmrcLocations; - public NpmPathResolver(File explicitNpmExecutable, File explicitNpmrcFile, File... additionalNpmrcLocations) { + public NpmPathResolver(File explicitNpmExecutable, File explicitNodeExecutable, File explicitNpmrcFile, List additionalNpmrcLocations) { this.explicitNpmExecutable = explicitNpmExecutable; + this.explicitNodeExecutable = explicitNodeExecutable; this.explicitNpmrcFile = explicitNpmrcFile; - this.additionalNpmrcLocations = Arrays.asList(additionalNpmrcLocations); + this.additionalNpmrcLocations = Collections.unmodifiableList(new ArrayList<>(additionalNpmrcLocations)); } public File resolveNpmExecutable() { @@ -40,6 +44,12 @@ public File resolveNpmExecutable() { .orElseThrow(() -> new IllegalStateException("Can't automatically determine npm executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage()))); } + public File resolveNodeExecutable() { + return Optional.ofNullable(this.explicitNodeExecutable) + .orElseGet(() -> NodeExecutableResolver.tryFindNextTo(resolveNpmExecutable()) + .orElseThrow(() -> new IllegalStateException("Can't automatically determine node executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage()))); + } + public String resolveNpmrcContent() { File npmrcFile = Optional.ofNullable(this.explicitNpmrcFile) .orElseGet(() -> new NpmrcResolver(additionalNpmrcLocations).tryFind() diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java index 8be94a9ea3..1675a4aa4e 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java @@ -28,9 +28,12 @@ class NpmProcess { private final File npmExecutable; - NpmProcess(File workingDir, File npmExecutable) { + private final File nodeExecutable; + + NpmProcess(File workingDir, File npmExecutable, File nodeExecutable) { this.workingDir = workingDir; this.npmExecutable = npmExecutable; + this.nodeExecutable = nodeExecutable; } void install() { @@ -61,11 +64,12 @@ private void npmAwait(String... args) { private Process npm(String... args) { List processCommand = processCommand(args); try { - return new ProcessBuilder() + ProcessBuilder processBuilder = new ProcessBuilder() .inheritIO() .directory(this.workingDir) - .command(processCommand) - .start(); + .command(processCommand); + addEnvironmentVariables(processBuilder); + return processBuilder.start(); } catch (IOException e) { throw new NpmProcessException("Failed to launch npm command '" + commandLine(args) + "'.", e); } @@ -78,6 +82,10 @@ private List processCommand(String... args) { return command; } + private void addEnvironmentVariables(ProcessBuilder processBuilder) { + processBuilder.environment().put("PATH", this.nodeExecutable.getParentFile().getAbsolutePath() + File.pathSeparator + System.getenv("PATH")); + } + private String commandLine(String... args) { return "npm " + Arrays.stream(args).collect(Collectors.joining(" ")); } 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 22a162eb0b..20ff71d08e 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -74,9 +74,11 @@ private static class State extends NpmFormatterStepStateBase implements Serializ "/com/diffplug/spotless/npm/common-serve.js", "/com/diffplug/spotless/npm/prettier-serve.js"), npmPathResolver.resolveNpmrcContent()), - projectDir, - buildDir, - npmPathResolver.resolveNpmExecutable()); + new NpmFormatterStepLocations( + projectDir, + buildDir, + npmPathResolver.resolveNpmExecutable(), + npmPathResolver.resolveNodeExecutable())); this.prettierConfig = requireNonNull(prettierConfig); } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java index 98d694ec33..14d6b1bbd0 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java @@ -80,9 +80,11 @@ public State(String stepName, Map versions, File projectDir, Fil "/com/diffplug/spotless/npm/common-serve.js", "/com/diffplug/spotless/npm/tsfmt-serve.js"), npmPathResolver.resolveNpmrcContent()), - projectDir, - buildDir, - npmPathResolver.resolveNpmExecutable()); + new NpmFormatterStepLocations( + projectDir, + buildDir, + npmPathResolver.resolveNpmExecutable(), + npmPathResolver.resolveNodeExecutable())); this.buildDir = requireNonNull(buildDir); this.configFile = configFile; this.inlineTsFmtSettings = inlineTsFmtSettings == null ? new TreeMap<>() : new TreeMap<>(inlineTsFmtSettings); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index 4e33c45ed0..8012102200 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java @@ -23,6 +23,7 @@ import java.nio.charset.Charset; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; @@ -524,6 +525,9 @@ public abstract static class NpmStepConfig> { @Nullable protected Object npmFile; + @Nullable + protected Object nodeFile; + @Nullable protected Object npmrcFile; @@ -543,6 +547,13 @@ public T npmExecutable(final Object npmFile) { return (T) this; } + @SuppressWarnings("unchecked") + public T nodeExecutable(final Object nodeFile) { + this.nodeFile = nodeFile; + replaceStep(); + return (T) this; + } + public T npmrc(final Object npmrcFile) { this.npmrcFile = npmrcFile; replaceStep(); @@ -553,6 +564,10 @@ File npmFileOrNull() { return fileOrNull(npmFile); } + File nodeFileOrNull() { + return fileOrNull(nodeFile); + } + File npmrcFileOrNull() { return fileOrNull(npmrcFile); } @@ -604,7 +619,7 @@ protected FormatterStep createStep() { provisioner(), project.getProjectDir(), project.getBuildDir(), - new NpmPathResolver(npmFileOrNull(), npmrcFileOrNull(), project.getProjectDir(), project.getRootDir()), + new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(), Arrays.asList(project.getProjectDir(), project.getRootDir())), new com.diffplug.spotless.npm.PrettierConfig( this.prettierConfigFile != null ? project.file(this.prettierConfigFile) : null, this.prettierConfig)); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java index dd476b6a69..e829c2b53a 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java @@ -17,6 +17,7 @@ import static java.util.Objects.requireNonNull; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -107,7 +108,7 @@ public FormatterStep createStep() { provisioner(), project.getProjectDir(), project.getBuildDir(), - new NpmPathResolver(npmFileOrNull(), npmrcFileOrNull(), project.getProjectDir(), project.getRootDir()), + new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(), Arrays.asList(project.getProjectDir(), project.getRootDir())), eslintConfig()); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java index 0824caa769..2283afccff 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java @@ -17,6 +17,7 @@ import static java.util.Objects.requireNonNull; +import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.Objects; @@ -116,7 +117,7 @@ public FormatterStep createStep() { provisioner(), project.getProjectDir(), project.getBuildDir(), - new NpmPathResolver(npmFileOrNull(), npmrcFileOrNull(), project.getProjectDir(), project.getRootDir()), + new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(), Arrays.asList(project.getProjectDir(), project.getRootDir())), typedConfigFile(), config); } @@ -212,7 +213,7 @@ public FormatterStep createStep() { provisioner(), project.getProjectDir(), project.getBuildDir(), - new NpmPathResolver(npmFileOrNull(), npmrcFileOrNull(), project.getProjectDir(), project.getRootDir()), + new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(), Arrays.asList(project.getProjectDir(), project.getRootDir())), eslintConfig()); } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java new file mode 100644 index 0000000000..93bc4b699b --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java @@ -0,0 +1,59 @@ +/* + * 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. + * 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 org.assertj.core.api.Assertions; +import org.gradle.testkit.runner.BuildResult; +import org.junit.jupiter.api.Test; + +class NpmTestsWithoutNpmInstallationTest extends GradleIntegrationHarness { + + @Test + void useNodeFromNodeGradlePlugin() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " npmVersion = '8.19.2'", + " workDir = file(\"${buildDir}/nodejs\")", + " npmWorkDir = file(\"${buildDir}/npm\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm\")", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/npm/AbstractNpmFormatterStepFactory.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/npm/AbstractNpmFormatterStepFactory.java index 5467f4c3bc..e4a052106a 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/npm/AbstractNpmFormatterStepFactory.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/npm/AbstractNpmFormatterStepFactory.java @@ -18,6 +18,7 @@ import java.io.File; import java.util.AbstractMap; import java.util.Arrays; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Properties; @@ -34,6 +35,9 @@ public abstract class AbstractNpmFormatterStepFactory implements FormatterStepFa @Parameter private String npmExecutable; + @Parameter + private String nodeExecutable; + @Parameter private String npmrc; @@ -42,6 +46,11 @@ protected File npm(FormatterStepConfig stepConfig) { return npm; } + protected File node(FormatterStepConfig stepConfig) { + File node = nodeExecutable != null ? stepConfig.getFileLocator().locateFile(nodeExecutable) : null; + return node; + } + protected File npmrc(FormatterStepConfig stepConfig) { File npmrc = this.npmrc != null ? stepConfig.getFileLocator().locateFile(this.npmrc) : null; return npmrc; @@ -56,7 +65,7 @@ protected File baseDir(FormatterStepConfig stepConfig) { } protected NpmPathResolver npmPathResolver(FormatterStepConfig stepConfig) { - return new NpmPathResolver(npm(stepConfig), npmrc(stepConfig), baseDir(stepConfig)); + return new NpmPathResolver(npm(stepConfig), node(stepConfig), npmrc(stepConfig), Collections.singletonList(baseDir(stepConfig))); } protected boolean moreThanOneNonNull(Object... objects) { diff --git a/testlib/src/test/java/com/diffplug/spotless/npm/NpmFormatterStepCommonTests.java b/testlib/src/test/java/com/diffplug/spotless/npm/NpmFormatterStepCommonTests.java index dff105108a..60dd42801a 100644 --- a/testlib/src/test/java/com/diffplug/spotless/npm/NpmFormatterStepCommonTests.java +++ b/testlib/src/test/java/com/diffplug/spotless/npm/NpmFormatterStepCommonTests.java @@ -17,17 +17,22 @@ import java.io.File; import java.io.IOException; +import java.util.Collections; import com.diffplug.spotless.ResourceHarness; public abstract class NpmFormatterStepCommonTests extends ResourceHarness { protected NpmPathResolver npmPathResolver() { - return new NpmPathResolver(npmExecutable(), npmrc()); + return new NpmPathResolver(npmExecutable(), nodeExecutable(), npmrc(), Collections.emptyList()); } private File npmExecutable() { - return NpmExecutableResolver.tryFind().orElseThrow(() -> new IllegalStateException("cannot detect node binary")); + return NpmExecutableResolver.tryFind().orElseThrow(() -> new IllegalStateException("cannot detect npm binary")); + } + + private File nodeExecutable() { + return NodeExecutableResolver.tryFindNextTo(npmExecutable()).orElseThrow(() -> new IllegalStateException("cannot detect node binary")); } private File npmrc() { From f4f8da7a259fbe55fdb66fc97b31f6d55c47a266 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Tue, 17 Jan 2023 13:11:59 +0100 Subject: [PATCH 18/48] add test for supplying only npm when binary --- .../NpmTestsWithoutNpmInstallationTest.java | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java index 93bc4b699b..621d953868 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java @@ -22,7 +22,7 @@ class NpmTestsWithoutNpmInstallationTest extends GradleIntegrationHarness { @Test - void useNodeFromNodeGradlePlugin() throws Exception { + void useNodeAndNpmFromNodeGradlePlugin() throws Exception { setFile("build.gradle").toLines( "plugins {", " id 'com.diffplug.spotless'", @@ -39,12 +39,48 @@ void useNodeFromNodeGradlePlugin() throws Exception { "def prettierConfig = [:]", "prettierConfig['printWidth'] = 50", "prettierConfig['parser'] = 'typescript'", + "def npmExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.cmd' : ''", + "def nodeExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.exe' : ''", "spotless {", " format 'mytypescript', {", " target 'test.ts'", " prettier()", - " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm\")", - " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node\")", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm${npmExecExtension}\")", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node${nodeExecExtension}\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } + + @Test + void useNpmFromNodeGradlePlugin() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " workDir = file(\"${buildDir}/nodejs\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def npmExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.cmd' : ''", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm${npmExecExtension}\")", " .config(prettierConfig)", " }", "}"); From 27f9919da7cc5079cfb2823d6c65484c6e542bb9 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Tue, 17 Jan 2023 19:51:54 +0100 Subject: [PATCH 19/48] also allow to configure node executable only --- .../spotless/npm/NodeExecutableResolver.java | 5 +++ .../spotless/npm/NpmPathResolver.java | 41 ++++++++++++++++--- .../NpmTestsWithoutNpmInstallationTest.java | 34 +++++++++++++++ 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java index c9e04da620..eb30ae78b8 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java @@ -42,4 +42,9 @@ static Optional tryFindNextTo(File npmExecutable) { } return Optional.empty(); } + + public static String explainMessage() { + return "Spotless was unable to find a node executable.\n" + + "Either specify the node executable explicitly or make sure it can be found next to the npm executable."; + } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java index d18146188d..4759d2f914 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java @@ -38,16 +38,45 @@ public NpmPathResolver(File explicitNpmExecutable, File explicitNodeExecutable, this.additionalNpmrcLocations = Collections.unmodifiableList(new ArrayList<>(additionalNpmrcLocations)); } + /** + * Finds the npm executable to use. + *
+ * Either the explicit npm executable is returned, or - if an explicit node executable is configured - tries to find + * the npm executable relative to the node executable. + * Falls back to looking for npm on the user's system using {@link NpmExecutableResolver} + * + * @return the npm executable to use + * @throws IllegalStateException if no npm executable could be found + */ public File resolveNpmExecutable() { - return Optional.ofNullable(this.explicitNpmExecutable) - .orElseGet(() -> NpmExecutableResolver.tryFind() - .orElseThrow(() -> new IllegalStateException("Can't automatically determine npm executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage()))); + if (this.explicitNpmExecutable != null) { + return this.explicitNpmExecutable; + } + if (this.explicitNodeExecutable != null) { + File nodeExecutableCandidate = new File(this.explicitNodeExecutable.getParentFile(), NpmExecutableResolver.npmExecutableName()); + if (nodeExecutableCandidate.canExecute()) { + return nodeExecutableCandidate; + } + } + return NpmExecutableResolver.tryFind() + .orElseThrow(() -> new IllegalStateException("Can't automatically determine npm executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage())); } + /** + * Finds the node executable to use. + *
+ * Either the explicit node executable is returned, or tries to find the node executable relative to the npm executable + * found by {@link #resolveNpmExecutable()}. + * @return the node executable to use + * @throws IllegalStateException if no node executable could be found + */ public File resolveNodeExecutable() { - return Optional.ofNullable(this.explicitNodeExecutable) - .orElseGet(() -> NodeExecutableResolver.tryFindNextTo(resolveNpmExecutable()) - .orElseThrow(() -> new IllegalStateException("Can't automatically determine node executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage()))); + if (this.explicitNodeExecutable != null) { + return this.explicitNodeExecutable; + } + File npmExecutable = resolveNpmExecutable(); + return NodeExecutableResolver.tryFindNextTo(npmExecutable) + .orElseThrow(() -> new IllegalStateException("Can't automatically determine node executable and none was specifically supplied!\n\n" + NodeExecutableResolver.explainMessage())); } public String resolveNpmrcContent() { diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java index 621d953868..8147c79228 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java @@ -92,4 +92,38 @@ void useNpmFromNodeGradlePlugin() throws Exception { Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); } + + @Test + void useNpmNextToConfiguredNodePluginFromNodeGradlePlugin() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " workDir = file(\"${buildDir}/nodejs\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def nodeExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.exe' : ''", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node${nodeExecExtension}\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } } From a96d92c0f524f71fc73476ffed6ab8f6b0425308 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Tue, 17 Jan 2023 20:46:32 +0100 Subject: [PATCH 20/48] add tests that verifies issue #1499 --- .../NpmTestsWithoutNpmInstallationTest.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java index 8147c79228..d5c4e8f092 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java @@ -17,6 +17,7 @@ import org.assertj.core.api.Assertions; import org.gradle.testkit.runner.BuildResult; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; class NpmTestsWithoutNpmInstallationTest extends GradleIntegrationHarness { @@ -59,6 +60,46 @@ void useNodeAndNpmFromNodeGradlePlugin() throws Exception { assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); } + @Test + @Disabled("This test is disabled because we currently don't support using npm/node installed by the" + + "node-gradle-plugin as the installation takes place in the *execution* phase, but spotless needs it in the *configuration* phase.") + void useNodeAndNpmFromNodeGradlePluginInOneSweep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " npmVersion = '8.19.2'", + " workDir = file(\"${buildDir}/nodejs\")", + " npmWorkDir = file(\"${buildDir}/npm\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def npmExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.cmd' : ''", + "def nodeExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.exe' : ''", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm${npmExecExtension}\")", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node${nodeExecExtension}\")", + " .config(prettierConfig)", + " }", + "}", + "tasks.named('spotlessMytypescript').configure {", + " it.dependsOn('nodeSetup', 'npmSetup')", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } + @Test void useNpmFromNodeGradlePlugin() throws Exception { setFile("build.gradle").toLines( From b9437cd78b16d4ef2aeee942bc8d7b6f367068ef Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Wed, 18 Jan 2023 13:02:15 +0100 Subject: [PATCH 21/48] add maven test to verify dynamically installed npm works --- .../maven/MavenIntegrationHarness.java | 27 +++++--- .../maven/MultiModuleProjectTest.java | 2 +- .../spotless/maven/SpotlessCheckMojoTest.java | 1 + .../incremental/UpToDateCheckingTest.java | 3 +- .../maven/npm/NpmFrontendMavenPlugin.java | 66 +++++++++++++++++++ ...namicallyInstalledNpmInstallationTest.java | 48 ++++++++++++++ .../src/test/resources/pom-test.xml.mustache | 1 + 7 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java create mode 100644 plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmTestsWithDynamicallyInstalledNpmInstallationTest.java 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 5a724408c8..f4e94e573c 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 @@ -59,6 +59,7 @@ public class MavenIntegrationHarness extends ResourceHarness { private static final String EXECUTIONS = "executions"; private static final String MODULES = "modules"; private static final String DEPENDENCIES = "dependencies"; + private static final String PLUGINS = "plugins"; private static final String MODULE_NAME = "name"; private static final int REMOTE_DEBUG_PORT = 5005; @@ -151,6 +152,10 @@ protected void writePomWithPrettierSteps(String includes, String... steps) throw writePom(formats(groupWithSteps("format", including(includes), steps))); } + protected void writePomWithPrettierSteps(String[] plugins, String includes, String... steps) throws IOException { + writePom(null, formats(groupWithSteps("format", including(includes), steps)), null, plugins); + } + protected void writePomWithPomSteps(String... steps) throws IOException { writePom(groupWithSteps("pom", including("pom_test.xml"), steps)); } @@ -168,11 +173,11 @@ protected void writePomWithYamlSteps(String... steps) throws IOException { } protected void writePom(String... configuration) throws IOException { - writePom(null, configuration, null); + writePom(null, configuration, null, null); } - protected void writePom(String[] executions, String[] configuration, String[] dependencies) throws IOException { - String pomXmlContent = createPomXmlContent(null, executions, configuration, dependencies); + protected void writePom(String[] executions, String[] configuration, String[] dependencies, String[] plugins) throws IOException { + String pomXmlContent = createPomXmlContent(null, executions, configuration, dependencies, plugins); setFile("pom.xml").toContent(pomXmlContent); } @@ -203,17 +208,17 @@ protected MavenRunner mavenRunnerWithRemoteDebug() throws IOException { return mavenRunner().withRemoteDebug(REMOTE_DEBUG_PORT); } - protected String createPomXmlContent(String pluginVersion, String[] executions, String[] configuration, String[] dependencies) throws IOException { - return createPomXmlContent("/pom-test.xml.mustache", pluginVersion, executions, configuration, dependencies); + protected String createPomXmlContent(String pluginVersion, String[] executions, String[] configuration, String[] dependencies, String[] plugins) throws IOException { + return createPomXmlContent("/pom-test.xml.mustache", pluginVersion, executions, configuration, dependencies, plugins); } - protected String createPomXmlContent(String pomTemplate, String pluginVersion, String[] executions, String[] configuration, String[] dependencies) throws IOException { - Map params = buildPomXmlParams(pluginVersion, executions, configuration, null, dependencies); + protected String createPomXmlContent(String pomTemplate, String pluginVersion, String[] executions, String[] configuration, String[] dependencies, String[] plugins) throws IOException { + Map params = buildPomXmlParams(pluginVersion, executions, configuration, null, dependencies, plugins); return createPomXmlContent(pomTemplate, params); } protected String createPomXmlContent(String pluginVersion, String[] executions, String[] configuration) throws IOException { - return createPomXmlContent(pluginVersion, executions, configuration, null); + return createPomXmlContent(pluginVersion, executions, configuration, null, null); } protected String createPomXmlContent(String pomTemplate, Map params) throws IOException { @@ -226,7 +231,7 @@ protected String createPomXmlContent(String pomTemplate, Map par } } - protected static Map buildPomXmlParams(String pluginVersion, String[] executions, String[] configuration, String[] modules, String[] dependencies) { + protected static Map buildPomXmlParams(String pluginVersion, String[] executions, String[] configuration, String[] modules, String[] dependencies, String[] plugins) { Map params = new HashMap<>(); params.put(SPOTLESS_MAVEN_PLUGIN_VERSION, pluginVersion == null ? getSystemProperty(SPOTLESS_MAVEN_PLUGIN_VERSION) : pluginVersion); @@ -247,6 +252,10 @@ protected static Map buildPomXmlParams(String pluginVersion, Str params.put(DEPENDENCIES, String.join("\n", dependencies)); } + if (plugins != null) { + params.put(PLUGINS, String.join("\n", plugins)); + } + return params; } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MultiModuleProjectTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MultiModuleProjectTest.java index ae8378c566..6206affc04 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MultiModuleProjectTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MultiModuleProjectTest.java @@ -145,7 +145,7 @@ private void createRootPom() throws IOException { modulesList.addAll(subProjects.keySet()); String[] modules = modulesList.toArray(new String[0]); - Map rootPomParams = buildPomXmlParams(null, null, configuration, modules, null); + Map rootPomParams = buildPomXmlParams(null, null, configuration, modules, null, null); setFile("pom.xml").toContent(createPomXmlContent("/multi-module/pom-parent.xml.mustache", rootPomParams)); } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpotlessCheckMojoTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpotlessCheckMojoTest.java index 566851c26c..92a78e3923 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpotlessCheckMojoTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpotlessCheckMojoTest.java @@ -62,6 +62,7 @@ void testSpotlessCheckBindingToVerifyPhase() throws Exception { " ${basedir}/license.txt", " ", ""}, + null, null); testSpotlessCheck(UNFORMATTED_FILE, "verify", true); diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/UpToDateCheckingTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/UpToDateCheckingTest.java index 6afcba7430..0a9fe8f2d4 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/UpToDateCheckingTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/UpToDateCheckingTest.java @@ -98,7 +98,8 @@ private void writePomWithPluginManagementAndDependency() throws IOException { " javax.inject", " 1", " ", - ""})); + ""}, + null)); } @Test diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java new file mode 100644 index 0000000000..3d20170fab --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java @@ -0,0 +1,66 @@ +/* + * 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.npm; + +/** + * Helper class to configure a maven pom to use frontend-maven-plugin. + * @see frontend-maven-plugin on github + */ +public final class NpmFrontendMavenPlugin { + + public static final String GROUP_ID = "com.github.eirslett"; + public static final String ARTIFACT_ID = "frontend-maven-plugin"; + public static final String VERSION = "1.11.3"; // for now, we stick with this version, as it is the last to support maven 3.1 (see pom-test.xml.mustache) + + public static final String GOAL_INSTALL_NODE_AND_NPM = "install-node-and-npm"; + + public static final String INSTALL_DIRECTORY = "target"; + + private NpmFrontendMavenPlugin() { + // prevent instantiation + } + + public static String[] pomPluginLines(String nodeVersion, String npmVersion) { + return new String[]{ + "", + String.format(" %s", GROUP_ID), + String.format(" %s", ARTIFACT_ID), + String.format(" %s", VERSION), + " ", + " ", + " install node and npm", + " ", + String.format(" %s", GOAL_INSTALL_NODE_AND_NPM), + " ", + " ", + " ", + " ", + (nodeVersion != null ? " " + nodeVersion + "" : ""), + (npmVersion != null ? " " + npmVersion + "" : ""), + String.format(" %s", INSTALL_DIRECTORY), + " ", + "" + }; + } + + public static String installNpmMavenGoal() { + return String.format("%s:%s:%s", GROUP_ID, ARTIFACT_ID, GOAL_INSTALL_NODE_AND_NPM); + } + + public static String installedNpmPath() { + return String.format("%s/node/npm", INSTALL_DIRECTORY); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmTestsWithDynamicallyInstalledNpmInstallationTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmTestsWithDynamicallyInstalledNpmInstallationTest.java new file mode 100644 index 0000000000..78830bf09d --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmTestsWithDynamicallyInstalledNpmInstallationTest.java @@ -0,0 +1,48 @@ +/* + * 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.npm; + +import static com.diffplug.spotless.maven.npm.NpmFrontendMavenPlugin.installNpmMavenGoal; +import static com.diffplug.spotless.maven.npm.NpmFrontendMavenPlugin.installedNpmPath; +import static com.diffplug.spotless.maven.npm.NpmFrontendMavenPlugin.pomPluginLines; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +public class NpmTestsWithDynamicallyInstalledNpmInstallationTest extends MavenIntegrationHarness { + + @Test + void useDownloadedNpmInstallation() throws Exception { + writePomWithPrettierSteps( + pomPluginLines("v18.13.0", null), + "src/main/typescript/test.ts", + "", + " " + installedNpmPath() + "", + ""); + + String kind = "typescript"; + String suffix = "ts"; + String configPath = ".prettierrc.yml"; + setFile(configPath).toResource("npm/prettier/filetypes/" + kind + "/" + ".prettierrc.yml"); + String path = "src/main/" + kind + "/test." + suffix; + setFile(path).toResource("npm/prettier/filetypes/" + kind + "/" + kind + ".dirty"); + + mavenRunner().withArguments(installNpmMavenGoal(), "spotless:apply").runNoError(); + assertFile(path).sameAsResource("npm/prettier/filetypes/" + kind + "/" + kind + ".clean"); + } + +} diff --git a/plugin-maven/src/test/resources/pom-test.xml.mustache b/plugin-maven/src/test/resources/pom-test.xml.mustache index 122f5d9218..6db3cb0c15 100644 --- a/plugin-maven/src/test/resources/pom-test.xml.mustache +++ b/plugin-maven/src/test/resources/pom-test.xml.mustache @@ -21,6 +21,7 @@ + {{{plugins}}} com.diffplug.spotless spotless-maven-plugin From 789b9d210a4b1555f0eee41d7e178798ecdd16f1 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Wed, 18 Jan 2023 14:22:13 +0100 Subject: [PATCH 22/48] add nodeExecutable configuration to README --- plugin-gradle/README.md | 11 ++++++++--- plugin-maven/README.md | 10 ++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index f2567a5924..11f7866644 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -844,18 +844,23 @@ spotless { ### npm detection Prettier is based on NodeJS, so a working NodeJS installation (especially npm) is required on the host running spotless. -Spotless will try to auto-discover an npm installation. If that is not working for you, it is possible to directly configure the npm binary to use. +Spotless will try to auto-discover an npm installation. If that is not working for you, it is possible to directly configure the npm +and/or node binary to use. ```gradle spotless { format 'javascript', { - prettier().npmExecutable('/usr/bin/npm').config(...) + prettier().npmExecutable('/usr/bin/npm').nodeExecutable('/usr/bin/node').config(...) ``` +If you provide both `npmExecutable` and `nodeExecutable`, spotless will use these paths. If you specify only one of the +two, spotless will assume the other one is in the same directory. + ### `.npmrc` detection Spotless picks up npm configuration stored in a `.npmrc` file either in the project directory or in your user home. -Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with `npmExecutable`, of course.) +Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with +`npmExecutable` and `nodeExecutable`, of course.) ```gradle spotless { diff --git a/plugin-maven/README.md b/plugin-maven/README.md index 2a455dfe07..dc3795927a 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -1050,17 +1050,23 @@ Since spotless uses the actual npm prettier package behind the scenes, it is pos ### npm detection Prettier is based on NodeJS, so to use it, a working NodeJS installation (especially npm) is required on the host running spotless. -Spotless will try to auto-discover an npm installation. If that is not working for you, it is possible to directly configure the npm binary to use. +Spotless will try to auto-discover an npm installation. If that is not working for you, it is possible to directly configure the npm +and/or node binary to use. ```xml /usr/bin/npm + /usr/bin/node ``` +If you provide both `npmExecutable` and `nodeExecutable`, spotless will use these paths. If you specify only one of the +two, spotless will assume the other one is in the same directory. + ### `.npmrc` detection Spotless picks up npm configuration stored in a `.npmrc` file either in the project directory or in your user home. -Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with `npmExecutable`, of course.) +Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with +`npmExecutable` and `nodeExecutable`, of course.) ```xml From a9c79aee0929dc15ac1683cf64831d85f589e7d9 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Wed, 18 Jan 2023 14:31:57 +0100 Subject: [PATCH 23/48] add `nodeExecutable` to changes.md --- CHANGES.md | 1 + plugin-gradle/CHANGES.md | 1 + plugin-maven/CHANGES.md | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 60c723f62d..dee5bb6175 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added * `ProcessRunner` has added some convenience methods so it can be used for maven testing. ([#1496](https://github.com/diffplug/spotless/pull/1496)) +* Allow to specify node executable for node-based formatters using `nodeExecutable` parameter ([#1500](https://github.com/diffplug/spotless/pull/1500)) ### Fixed * The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494) ### Changes diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 63e5b43d74..58142c6755 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -4,6 +4,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added +* Allow to specify node executable for node-based formatters using `nodeExecutable` parameter ([#1500](https://github.com/diffplug/spotless/pull/1500)) ### Fixed * The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494) ### Changes diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 1bb49898f1..d93893f35b 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -4,6 +4,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added +* Allow to specify node executable for node-based formatters using `nodeExecutable` parameter ([#1500](https://github.com/diffplug/spotless/pull/1500)) ### Fixed * The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494) ### Changes From 6b9b870c8754c4af9967ea094b1049f4dc941d25 Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 23:08:33 +0400 Subject: [PATCH 24/48] Rely on generic JsonNode --- .../spotless/glue/yaml/JacksonYamlFormatterFunc.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 edb350c559..ae6b85530b 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 @@ -22,6 +22,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ContainerNode; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; @@ -60,13 +61,11 @@ protected JsonFactory makeJsonFactory() { @Override protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { - //if (true) - // throw new IllegalArgumentException("input = \r\n" + input); 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, ContainerNode.class).readAll(); + List documents = objectMapper.readValues(yamlParser, JsonNode.class).readAll(); // https://github.com/FasterXML/jackson-dataformats-text/issues/66#issuecomment-554265055 // https://github.com/FasterXML/jackson-dataformats-text/issues/66#issuecomment-554265055 From 6b6e854e19b0969d23918ae9bd9ec705f25fc46d Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 18 Jan 2023 23:20:27 +0400 Subject: [PATCH 25/48] Fix style --- .../diffplug/spotless/glue/yaml/JacksonYamlFormatterFunc.java | 1 - 1 file changed, 1 deletion(-) 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 ae6b85530b..89304d8d0a 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 @@ -24,7 +24,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ContainerNode; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLFactoryBuilder; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; From e8f0d2d27bb542987b8cc97b7a4c2b5a04880306 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Wed, 18 Jan 2023 19:59:56 +0100 Subject: [PATCH 26/48] try fix windows build: set binary name for win --- .../spotless/GradleIntegrationHarness.java | 47 ++- .../NpmTestsWithoutNpmInstallationTest.java | 284 ++++++++++-------- .../maven/npm/NpmFrontendMavenPlugin.java | 2 +- 3 files changed, 193 insertions(+), 140 deletions(-) diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java index 9e2f490429..1763fd088f 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.List; import java.util.ListIterator; +import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -117,11 +118,34 @@ protected GradleRunner gradleRunner() throws IOException { } /** Dumps the complete file contents of the folder to the console. */ - protected String getContents() throws IOException { + protected String getContents() { return getContents(subPath -> !subPath.startsWith(".gradle")); } - protected String getContents(Predicate subpathsToInclude) throws IOException { + protected String getContents(Predicate subpathsToInclude) { + return StringPrinter.buildString(printer -> Errors.rethrow().run(() -> iterateFiles(subpathsToInclude, (subpath, file) -> { + printer.println("### " + subpath + " ###"); + try { + printer.println(read(subpath)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }))); + } + + /** Dumps the filtered file listing of the folder to the console. */ + protected String listFiles(Predicate subpathsToInclude) { + return StringPrinter.buildString(printer -> iterateFiles(subpathsToInclude, (subPath, file) -> { + printer.println(subPath + " [" + getFileAttributes(file) + "]"); + })); + } + + /** Dumps the file listing of the folder to the console. */ + protected String listFiles() { + return listFiles(subPath -> !subPath.startsWith(".gradle")); + } + + protected void iterateFiles(Predicate subpathsToInclude, BiConsumer consumer) { TreeDef treeDef = TreeDef.forFile(Errors.rethrow()); List files = TreeStream.depthFirst(treeDef, rootFolder()) .filter(File::isFile) @@ -129,16 +153,17 @@ protected String getContents(Predicate subpathsToInclude) throws IOExcep ListIterator iterator = files.listIterator(files.size()); int rootLength = rootFolder().getAbsolutePath().length() + 1; - return StringPrinter.buildString(printer -> Errors.rethrow().run(() -> { - while (iterator.hasPrevious()) { - File file = iterator.previous(); - String subPath = file.getAbsolutePath().substring(rootLength); - if (subpathsToInclude.test(subPath)) { - printer.println("### " + subPath + " ###"); - printer.println(read(subPath)); - } + while (iterator.hasPrevious()) { + File file = iterator.previous(); + String subPath = file.getAbsolutePath().substring(rootLength); + if (subpathsToInclude.test(subPath)) { + consumer.accept(subPath, file); } - })); + } + } + + protected String getFileAttributes(File file) { + return (file.canRead() ? "r" : "-") + (file.canWrite() ? "w" : "-") + (file.canExecute() ? "x" : "-"); } protected void checkRunsThenUpToDate() throws IOException { diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java index d5c4e8f092..916d853920 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java @@ -20,151 +20,179 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import com.diffplug.common.base.Predicates; + class NpmTestsWithoutNpmInstallationTest extends GradleIntegrationHarness { @Test void useNodeAndNpmFromNodeGradlePlugin() throws Exception { - setFile("build.gradle").toLines( - "plugins {", - " id 'com.diffplug.spotless'", - " id 'com.github.node-gradle.node' version '3.5.1'", - "}", - "repositories { mavenCentral() }", - "node {", - " download = true", - " version = '18.13.0'", - " npmVersion = '8.19.2'", - " workDir = file(\"${buildDir}/nodejs\")", - " npmWorkDir = file(\"${buildDir}/npm\")", - "}", - "def prettierConfig = [:]", - "prettierConfig['printWidth'] = 50", - "prettierConfig['parser'] = 'typescript'", - "def npmExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.cmd' : ''", - "def nodeExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.exe' : ''", - "spotless {", - " format 'mytypescript', {", - " target 'test.ts'", - " prettier()", - " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm${npmExecExtension}\")", - " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node${nodeExecExtension}\")", - " .config(prettierConfig)", - " }", - "}"); - setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); - // make sure node binary is there - gradleRunner().withArguments("nodeSetup", "npmSetup").build(); - // then run spotless using that node installation - final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); - Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); - assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + try { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " npmVersion = '8.19.2'", + " workDir = file(\"${buildDir}/nodejs\")", + " npmWorkDir = file(\"${buildDir}/npm\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def npmExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/npm.cmd' : '/bin/npm'", + "def nodeExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/node.exe' : '/bin/node'", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}${npmExec}\")", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}${nodeExec}\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } catch (Exception e) { + printContents(); + throw e; + } + } + + private void printContents() { + System.out.println("************* Folder contents **************************"); + System.out.println(listFiles(Predicates.and(path -> !path.startsWith(".gradle"), path -> !path.contains("/node_modules/"), path -> !path.contains("/include/")))); + System.out.println("********************************************************"); } @Test @Disabled("This test is disabled because we currently don't support using npm/node installed by the" + "node-gradle-plugin as the installation takes place in the *execution* phase, but spotless needs it in the *configuration* phase.") void useNodeAndNpmFromNodeGradlePluginInOneSweep() throws Exception { - setFile("build.gradle").toLines( - "plugins {", - " id 'com.diffplug.spotless'", - " id 'com.github.node-gradle.node' version '3.5.1'", - "}", - "repositories { mavenCentral() }", - "node {", - " download = true", - " version = '18.13.0'", - " npmVersion = '8.19.2'", - " workDir = file(\"${buildDir}/nodejs\")", - " npmWorkDir = file(\"${buildDir}/npm\")", - "}", - "def prettierConfig = [:]", - "prettierConfig['printWidth'] = 50", - "prettierConfig['parser'] = 'typescript'", - "def npmExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.cmd' : ''", - "def nodeExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.exe' : ''", - "spotless {", - " format 'mytypescript', {", - " target 'test.ts'", - " prettier()", - " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm${npmExecExtension}\")", - " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node${nodeExecExtension}\")", - " .config(prettierConfig)", - " }", - "}", - "tasks.named('spotlessMytypescript').configure {", - " it.dependsOn('nodeSetup', 'npmSetup')", - "}"); - setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); - final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); - Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); - assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + try { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " npmVersion = '8.19.2'", + " workDir = file(\"${buildDir}/nodejs\")", + " npmWorkDir = file(\"${buildDir}/npm\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def npmExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/npm.cmd' : '/bin/npm'", + "def nodeExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/node.exe' : '/bin/node'", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}${npmExec}\")", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}${nodeExec}\")", + " .config(prettierConfig)", + " }", + "}", + "tasks.named('spotlessMytypescript').configure {", + " it.dependsOn('nodeSetup', 'npmSetup')", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } catch (Exception e) { + printContents(); + throw e; + } } @Test void useNpmFromNodeGradlePlugin() throws Exception { - setFile("build.gradle").toLines( - "plugins {", - " id 'com.diffplug.spotless'", - " id 'com.github.node-gradle.node' version '3.5.1'", - "}", - "repositories { mavenCentral() }", - "node {", - " download = true", - " version = '18.13.0'", - " workDir = file(\"${buildDir}/nodejs\")", - "}", - "def prettierConfig = [:]", - "prettierConfig['printWidth'] = 50", - "prettierConfig['parser'] = 'typescript'", - "def npmExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.cmd' : ''", - "spotless {", - " format 'mytypescript', {", - " target 'test.ts'", - " prettier()", - " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm${npmExecExtension}\")", - " .config(prettierConfig)", - " }", - "}"); - setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); - // make sure node binary is there - gradleRunner().withArguments("nodeSetup", "npmSetup").build(); - // then run spotless using that node installation - final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); - Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); - assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + try { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " workDir = file(\"${buildDir}/nodejs\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def npmExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/npm.cmd' : '/bin/npm'", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}${npmExec}\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } catch (Exception e) { + printContents(); + throw e; + } } @Test void useNpmNextToConfiguredNodePluginFromNodeGradlePlugin() throws Exception { - setFile("build.gradle").toLines( - "plugins {", - " id 'com.diffplug.spotless'", - " id 'com.github.node-gradle.node' version '3.5.1'", - "}", - "repositories { mavenCentral() }", - "node {", - " download = true", - " version = '18.13.0'", - " workDir = file(\"${buildDir}/nodejs\")", - "}", - "def prettierConfig = [:]", - "prettierConfig['printWidth'] = 50", - "prettierConfig['parser'] = 'typescript'", - "def nodeExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.exe' : ''", - "spotless {", - " format 'mytypescript', {", - " target 'test.ts'", - " prettier()", - " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node${nodeExecExtension}\")", - " .config(prettierConfig)", - " }", - "}"); - setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); - // make sure node binary is there - gradleRunner().withArguments("nodeSetup", "npmSetup").build(); - // then run spotless using that node installation - final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); - Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); - assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + try { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " workDir = file(\"${buildDir}/nodejs\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def nodeExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/node.exe' : '/bin/node'", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}${nodeExec}\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } catch (Exception e) { + printContents(); + throw e; + } } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java index 3d20170fab..de030ab81c 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java @@ -61,6 +61,6 @@ public static String installNpmMavenGoal() { } public static String installedNpmPath() { - return String.format("%s/node/npm", INSTALL_DIRECTORY); + return String.format("%s/node/npm%s", INSTALL_DIRECTORY, System.getProperty("os.name").toLowerCase().contains("win") ? ".cmd" : ""); } } From 5315110b4b42dbc4c105ce06d0cf153965877cc3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Jan 2023 14:20:10 +0000 Subject: [PATCH 27/48] chore(deps): update plugin org.gradle.test-retry to v1.5.1 --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index 57498bf23f..fb9e7dcf0b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,7 +12,7 @@ pluginManagement { // https://github.com/diffplug/goomph/blob/main/CHANGES.md id 'com.diffplug.p2.asmaven' version '3.27.0' // DO NOT UPDATE, see https://github.com/diffplug/spotless/pull/874 // https://github.com/gradle/test-retry-gradle-plugin/releases - id 'org.gradle.test-retry' version '1.5.0' + id 'org.gradle.test-retry' version '1.5.1' // https://github.com/radarsh/gradle-test-logger-plugin/blob/develop/CHANGELOG.md id 'com.adarshr.test-logger' version '3.2.0' // https://github.com/davidburstrom/version-compatibility-gradle-plugin/tags From 00e6282d2cab0c067614e3b2cd5138c47ee54bb1 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 19 Jan 2023 10:19:25 -0800 Subject: [PATCH 28/48] Add the Jackson steps to the root README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 637781d894..0743b69427 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ lib('java.RemoveUnusedImportsStep') +'{{yes}} | {{yes}} extra('java.EclipseJdtFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', lib('java.FormatAnnotationsStep') +'{{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}} |', lib('kotlin.KtLintStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', lib('kotlin.KtfmtStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', @@ -77,7 +78,7 @@ lib('python.BlackStep') +'{{yes}} | {{no}} lib('scala.ScalaFmtStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', lib('sql.DBeaverSQLFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', -lib('yaml.YamlJacksonStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |', +lib('yaml.JacksonYamlStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', '| [(Your FormatterStep here)](CONTRIBUTING.md#how-to-add-a-new-formatterstep) | {{no}} | {{no}} | {{no}} | {{no}} |', ].join('\n'); --> @@ -109,6 +110,7 @@ lib('yaml.YamlJacksonStep') +'{{no}} | {{yes}} | [`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: | | [`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: | | [`kotlin.KtLintStep`](lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`kotlin.KtfmtStep`](lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | @@ -123,7 +125,7 @@ lib('yaml.YamlJacksonStep') +'{{no}} | {{yes}} | [`scala.ScalaFmtStep`](lib/src/main/java/com/diffplug/spotless/scala/ScalaFmtStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`sql.DBeaverSQLFormatterStep`](lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`wtp.EclipseWtpFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/wtp/EclipseWtpFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | -| [`yaml.YamlJacksonStep`](lib/src/main/java/com/diffplug/spotless/yaml/YamlJacksonStep.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: | +| [`yaml.JacksonYamlStep`](lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [(Your FormatterStep here)](CONTRIBUTING.md#how-to-add-a-new-formatterstep) | :white_large_square: | :white_large_square: | :white_large_square: | :white_large_square: | From 10f04deb64ca671d8cc448665243dd74337e2ff3 Mon Sep 17 00:00:00 2001 From: Abel Keszei Date: Thu, 19 Jan 2023 22:06:16 +0100 Subject: [PATCH 29/48] move gson formatter to custom compile sourceset --- lib/build.gradle | 5 +- .../spotless/glue/gson/GsonFormatterFunc.java | 77 ++++++++++++++++++ .../json/gson/GsonBuilderWrapper.java | 54 ------------- .../spotless/json/gson/GsonConfig.java | 51 ++++++++++++ .../diffplug/spotless/json/gson/GsonStep.java | 78 ++++--------------- .../spotless/json/gson/GsonWrapper.java | 41 ---------- .../spotless/json/gson/GsonWrapperBase.java | 71 ----------------- .../json/gson/JsonElementWrapper.java | 40 ---------- .../spotless/json/gson/JsonObjectWrapper.java | 56 ------------- .../spotless/json/gson/JsonWriterWrapper.java | 48 ------------ .../gradle/spotless/JsonExtension.java | 21 ++--- .../diffplug/spotless/maven/json/Gson.java | 6 +- 12 files changed, 161 insertions(+), 387 deletions(-) create mode 100644 lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/json/gson/GsonBuilderWrapper.java create mode 100644 lib/src/main/java/com/diffplug/spotless/json/gson/GsonConfig.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapper.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapperBase.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/json/gson/JsonElementWrapper.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/json/gson/JsonObjectWrapper.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/json/gson/JsonWriterWrapper.java diff --git a/lib/build.gradle b/lib/build.gradle index 30c8fe8b15..bb1a389993 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -15,7 +15,8 @@ def NEEDS_GLUE = [ 'flexmark', 'diktat', 'scalafmt', - 'jackson' + 'jackson', + 'gson' ] for (glue in NEEDS_GLUE) { sourceSets.register(glue) { @@ -108,6 +109,8 @@ dependencies { // used for markdown formatting flexmarkCompileOnly 'com.vladsch.flexmark:flexmark-all:0.62.2' + + gsonCompileOnly 'com.google.code.gson:gson:2.10.1' } // we'll hold the core lib to a high standard diff --git a/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java b/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java new file mode 100644 index 0000000000..33db1faaa6 --- /dev/null +++ b/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java @@ -0,0 +1,77 @@ +package com.diffplug.spotless.glue.gson; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Collections; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.ThrowingEx; +import com.diffplug.spotless.json.gson.GsonConfig; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.stream.JsonWriter; + +public class GsonFormatterFunc implements FormatterFunc { + + private static final String FAILED_TO_PARSE_ERROR_MESSAGE = "Unable to format JSON"; + + private final Gson gson; + private final GsonConfig gsonConfig; + private final String generatedIndent; + + public GsonFormatterFunc(GsonConfig gsonConfig) { + GsonBuilder gsonBuilder = new GsonBuilder().serializeNulls(); + if (!gsonConfig.isEscapeHtml()) { + gsonBuilder = gsonBuilder.disableHtmlEscaping(); + } + this.gson = gsonBuilder.create(); + this.gsonConfig = gsonConfig; + this.generatedIndent = generateIndent(gsonConfig.getIndentSpaces()); + } + + @Override + public String apply(String inputString) { + String result; + if (inputString.isEmpty()) { + result = ""; + } else { + JsonElement jsonElement = gson.fromJson(inputString, JsonElement.class); + if (jsonElement == null) { + throw new AssertionError(FAILED_TO_PARSE_ERROR_MESSAGE); + } + if (gsonConfig.isSortByKeys() && jsonElement.isJsonObject()) { + jsonElement = sortByKeys(jsonElement.getAsJsonObject()); + } + try (StringWriter stringWriter = new StringWriter()) { + JsonWriter jsonWriter = new JsonWriter(stringWriter); + jsonWriter.setIndent(this.generatedIndent); + gson.toJson(jsonElement, jsonWriter); + result = stringWriter + "\n"; + } catch (IOException ioException) { + throw ThrowingEx.asRuntime(ioException); + } + } + return result; + } + + private JsonElement sortByKeys(JsonObject jsonObject) { + JsonObject result = new JsonObject(); + result.keySet().stream().sorted() + .forEach(key -> { + JsonElement element = jsonObject.get(key); + if (element.isJsonObject()) { + element = sortByKeys(element.getAsJsonObject()); + } + result.add(key, element); + }); + return result; + } + + private String generateIndent(int indentSpaces) { + return String.join("", Collections.nCopies(indentSpaces, " ")); + } + +} diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonBuilderWrapper.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonBuilderWrapper.java deleted file mode 100644 index c2e56f39b8..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonBuilderWrapper.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.json.gson; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -import com.diffplug.spotless.JarState; - -class GsonBuilderWrapper extends GsonWrapperBase { - - private final Constructor constructor; - private final Method serializeNullsMethod; - private final Method disableHtmlEscapingMethod; - private final Method createMethod; - - GsonBuilderWrapper(JarState jarState) { - Class clazz = loadClass(jarState.getClassLoader(), "com.google.gson.GsonBuilder"); - this.constructor = getConstructor(clazz); - this.serializeNullsMethod = getMethod(clazz, "serializeNulls"); - this.disableHtmlEscapingMethod = getMethod(clazz, "disableHtmlEscaping"); - this.createMethod = getMethod(clazz, "create"); - } - - Object createGsonBuilder() { - return newInstance(constructor); - } - - Object serializeNulls(Object gsonBuilder) { - return invoke(serializeNullsMethod, gsonBuilder); - } - - Object disableHtmlEscaping(Object gsonBuilder) { - return invoke(disableHtmlEscapingMethod, gsonBuilder); - } - - Object create(Object gsonBuilder) { - return invoke(createMethod, gsonBuilder); - } - -} diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonConfig.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonConfig.java new file mode 100644 index 0000000000..c63ad0aaba --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonConfig.java @@ -0,0 +1,51 @@ +package com.diffplug.spotless.json.gson; + +import java.io.Serializable; + +public class GsonConfig implements Serializable { + private static final long serialVersionUID = 6039715618937332633L; + + private boolean sortByKeys; + private boolean escapeHtml; + private int indentSpaces; + private String version; + + public GsonConfig(boolean sortByKeys, boolean escapeHtml, int indentSpaces, String version) { + this.sortByKeys = sortByKeys; + this.escapeHtml = escapeHtml; + this.indentSpaces = indentSpaces; + this.version = version; + } + + public boolean isSortByKeys() { + return sortByKeys; + } + + public void setSortByKeys(boolean sortByKeys) { + this.sortByKeys = sortByKeys; + } + + public boolean isEscapeHtml() { + return escapeHtml; + } + + public void setEscapeHtml(boolean escapeHtml) { + this.escapeHtml = escapeHtml; + } + + public int getIndentSpaces() { + return indentSpaces; + } + + public void setIndentSpaces(int indentSpaces) { + this.indentSpaces = indentSpaces; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java index 06519ae39d..624c4f2533 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java +++ b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java @@ -17,8 +17,8 @@ import java.io.IOException; import java.io.Serializable; -import java.io.StringWriter; -import java.util.Collections; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.Objects; import com.diffplug.spotless.FormatterFunc; @@ -29,77 +29,27 @@ public class GsonStep { private static final String MAVEN_COORDINATES = "com.google.code.gson:gson"; - public static FormatterStep create(int indentSpaces, boolean sortByKeys, boolean escapeHtml, String version, Provisioner provisioner) { + public static FormatterStep create(GsonConfig gsonConfig, Provisioner provisioner) { Objects.requireNonNull(provisioner, "provisioner cannot be null"); - return FormatterStep.createLazy("gson", () -> new State(indentSpaces, sortByKeys, escapeHtml, version, provisioner), State::toFormatter); + return FormatterStep.createLazy("gson", () -> new State(gsonConfig, provisioner), State::toFormatter); } private static final class State implements Serializable { - private static final long serialVersionUID = -1493479043249379485L; + private static final long serialVersionUID = -3240568265160440420L; - private final int indentSpaces; - private final boolean sortByKeys; - private final boolean escapeHtml; private final JarState jarState; + private final GsonConfig gsonConfig; - private State(int indentSpaces, boolean sortByKeys, boolean escapeHtml, String version, Provisioner provisioner) throws IOException { - this.indentSpaces = indentSpaces; - this.sortByKeys = sortByKeys; - this.escapeHtml = escapeHtml; - this.jarState = JarState.from(MAVEN_COORDINATES + ":" + version, provisioner); + private State(GsonConfig gsonConfig, Provisioner provisioner) throws IOException { + this.gsonConfig = gsonConfig; + this.jarState = JarState.from(MAVEN_COORDINATES + ":" + gsonConfig.getVersion(), provisioner); } - FormatterFunc toFormatter() { - JsonWriterWrapper jsonWriterWrapper = new JsonWriterWrapper(jarState); - JsonElementWrapper jsonElementWrapper = new JsonElementWrapper(jarState); - JsonObjectWrapper jsonObjectWrapper = new JsonObjectWrapper(jarState, jsonElementWrapper); - GsonBuilderWrapper gsonBuilderWrapper = new GsonBuilderWrapper(jarState); - GsonWrapper gsonWrapper = new GsonWrapper(jarState, jsonElementWrapper, jsonWriterWrapper); - - Object gsonBuilder = gsonBuilderWrapper.serializeNulls(gsonBuilderWrapper.createGsonBuilder()); - if (!escapeHtml) { - gsonBuilder = gsonBuilderWrapper.disableHtmlEscaping(gsonBuilder); - } - Object gson = gsonBuilderWrapper.create(gsonBuilder); - - return inputString -> { - String result; - if (inputString.isEmpty()) { - result = ""; - } else { - Object jsonElement = gsonWrapper.fromJson(gson, inputString, jsonElementWrapper.getWrappedClass()); - if (jsonElement == null) { - throw new AssertionError(GsonWrapperBase.FAILED_TO_PARSE_ERROR_MESSAGE); - } - if (sortByKeys && jsonElementWrapper.isJsonObject(jsonElement)) { - jsonElement = sortByKeys(jsonObjectWrapper, jsonElementWrapper, jsonElement); - } - try (StringWriter stringWriter = new StringWriter()) { - Object jsonWriter = jsonWriterWrapper.createJsonWriter(stringWriter); - jsonWriterWrapper.setIndent(jsonWriter, generateIndent(indentSpaces)); - gsonWrapper.toJson(gson, jsonElement, jsonWriter); - result = stringWriter + "\n"; - } - } - return result; - }; - } - - private Object sortByKeys(JsonObjectWrapper jsonObjectWrapper, JsonElementWrapper jsonElementWrapper, Object jsonObject) { - Object result = jsonObjectWrapper.createJsonObject(); - jsonObjectWrapper.keySet(jsonObject).stream().sorted() - .forEach(key -> { - Object element = jsonObjectWrapper.get(jsonObject, key); - if (jsonElementWrapper.isJsonObject(element)) { - element = sortByKeys(jsonObjectWrapper, jsonElementWrapper, element); - } - jsonObjectWrapper.add(result, key, element); - }); - return result; - } - - private String generateIndent(int indentSpaces) { - return String.join("", Collections.nCopies(indentSpaces, " ")); + FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, + InstantiationException, IllegalAccessException { + Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.gson.GsonFormatterFunc"); + Constructor constructor = formatterFunc.getConstructor(GsonConfig.class); + return (FormatterFunc) constructor.newInstance(gsonConfig); } } diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapper.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapper.java deleted file mode 100644 index eaca499eed..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapper.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.json.gson; - -import java.lang.reflect.Method; - -import com.diffplug.spotless.JarState; - -class GsonWrapper extends GsonWrapperBase { - - private final Method fromJsonMethod; - private final Method toJsonMethod; - - GsonWrapper(JarState jarState, JsonElementWrapper jsonElementWrapper, JsonWriterWrapper jsonWriterWrapper) { - Class clazz = loadClass(jarState.getClassLoader(), "com.google.gson.Gson"); - this.fromJsonMethod = getMethod(clazz, "fromJson", String.class, Class.class); - this.toJsonMethod = getMethod(clazz, "toJson", jsonElementWrapper.getWrappedClass(), jsonWriterWrapper.getWrappedClass()); - } - - Object fromJson(Object gson, String json, Class type) { - return invoke(fromJsonMethod, gson, json, type); - } - - void toJson(Object gson, Object jsonElement, Object jsonWriter) { - invoke(toJsonMethod, gson, jsonElement, jsonWriter); - } - -} diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapperBase.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapperBase.java deleted file mode 100644 index 24d12a5722..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapperBase.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.json.gson; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -abstract class GsonWrapperBase { - - static final String INCOMPATIBLE_ERROR_MESSAGE = "There was a problem interacting with Gson; maybe you set an incompatible version?"; - static final String FAILED_TO_PARSE_ERROR_MESSAGE = "Unable to format JSON"; - - protected final Class loadClass(ClassLoader classLoader, String className) { - try { - return classLoader.loadClass(className); - } catch (ClassNotFoundException cause) { - throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); - } - } - - protected final Constructor getConstructor(Class clazz, Class... argumentTypes) { - try { - return clazz.getConstructor(argumentTypes); - } catch (NoSuchMethodException cause) { - throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); - } - } - - protected final Method getMethod(Class clazz, String name, Class... argumentTypes) { - try { - return clazz.getMethod(name, argumentTypes); - } catch (NoSuchMethodException cause) { - throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); - } - } - - protected final T newInstance(Constructor constructor, Object... args) { - try { - return constructor.newInstance(args); - } catch (InstantiationException | IllegalAccessException cause) { - throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); - } catch (InvocationTargetException cause) { - throw new AssertionError(FAILED_TO_PARSE_ERROR_MESSAGE, cause.getCause()); - } - } - - protected Object invoke(Method method, Object targetObject, Object... args) { - try { - return method.invoke(targetObject, args); - } catch (IllegalAccessException cause) { - throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); - } catch (InvocationTargetException cause) { - throw new AssertionError(FAILED_TO_PARSE_ERROR_MESSAGE, cause.getCause()); - } - } - -} diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/JsonElementWrapper.java b/lib/src/main/java/com/diffplug/spotless/json/gson/JsonElementWrapper.java deleted file mode 100644 index ffdfa649ce..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/JsonElementWrapper.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.json.gson; - -import java.lang.reflect.Method; - -import com.diffplug.spotless.JarState; - -class JsonElementWrapper extends GsonWrapperBase { - - private final Class clazz; - private final Method isJsonObjectMethod; - - JsonElementWrapper(JarState jarState) { - this.clazz = loadClass(jarState.getClassLoader(), "com.google.gson.JsonElement"); - this.isJsonObjectMethod = getMethod(clazz, "isJsonObject"); - } - - boolean isJsonObject(Object jsonElement) { - return (boolean) invoke(isJsonObjectMethod, jsonElement); - } - - Class getWrappedClass() { - return clazz; - } - -} diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/JsonObjectWrapper.java b/lib/src/main/java/com/diffplug/spotless/json/gson/JsonObjectWrapper.java deleted file mode 100644 index 35ec0d876b..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/JsonObjectWrapper.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.json.gson; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.Set; - -import com.diffplug.spotless.JarState; - -class JsonObjectWrapper extends GsonWrapperBase { - - private final Constructor constructor; - private final Method keySetMethod; - private final Method getMethod; - private final Method addMethod; - - JsonObjectWrapper(JarState jarState, JsonElementWrapper jsonElementWrapper) { - Class clazz = loadClass(jarState.getClassLoader(), "com.google.gson.JsonObject"); - this.constructor = getConstructor(clazz); - this.keySetMethod = getMethod(clazz, "keySet"); - this.getMethod = getMethod(clazz, "get", String.class); - this.addMethod = getMethod(clazz, "add", String.class, jsonElementWrapper.getWrappedClass()); - } - - Object createJsonObject() { - return newInstance(constructor); - } - - @SuppressWarnings("unchecked") - Set keySet(Object jsonObject) { - return (Set) invoke(keySetMethod, jsonObject); - } - - Object get(Object jsonObject, String key) { - return invoke(getMethod, jsonObject, key); - } - - void add(Object jsonObject, String key, Object element) { - invoke(addMethod, jsonObject, key, element); - } - -} diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/JsonWriterWrapper.java b/lib/src/main/java/com/diffplug/spotless/json/gson/JsonWriterWrapper.java deleted file mode 100644 index c9d682e2c2..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/JsonWriterWrapper.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.json.gson; - -import java.io.Writer; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -import com.diffplug.spotless.JarState; - -class JsonWriterWrapper extends GsonWrapperBase { - - private final Class clazz; - private final Constructor constructor; - private final Method setIndentMethod; - - JsonWriterWrapper(JarState jarState) { - this.clazz = loadClass(jarState.getClassLoader(), "com.google.gson.stream.JsonWriter"); - this.constructor = getConstructor(clazz, Writer.class); - this.setIndentMethod = getMethod(clazz, "setIndent", String.class); - } - - Object createJsonWriter(Writer writer) { - return newInstance(constructor, writer); - } - - void setIndent(Object jsonWriter, String indent) { - invoke(setIndentMethod, jsonWriter, indent); - } - - Class getWrappedClass() { - return clazz; - } - -} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java index 1e9de2e831..c684dab291 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java @@ -23,11 +23,12 @@ import com.diffplug.spotless.json.JacksonJsonConfig; import com.diffplug.spotless.json.JacksonJsonStep; import com.diffplug.spotless.json.JsonSimpleStep; +import com.diffplug.spotless.json.gson.GsonConfig; import com.diffplug.spotless.json.gson.GsonStep; public class JsonExtension extends FormatExtension { private static final int DEFAULT_INDENTATION = 4; - private static final String DEFAULT_GSON_VERSION = "2.8.9"; + private static final String DEFAULT_GSON_VERSION = "2.10.1"; static final String NAME = "json"; @Inject @@ -47,8 +48,8 @@ public SimpleConfig simple() { return new SimpleConfig(DEFAULT_INDENTATION); } - public GsonConfig gson() { - return new GsonConfig(); + public GsonGradleConfig gson() { + return new GsonGradleConfig(); } public JacksonJsonGradleConfig jackson() { @@ -73,13 +74,13 @@ private FormatterStep createStep() { } } - public class GsonConfig { + public class GsonGradleConfig { private int indentSpaces; private boolean sortByKeys; private boolean escapeHtml; private String version; - public GsonConfig() { + public GsonGradleConfig() { this.indentSpaces = DEFAULT_INDENTATION; this.sortByKeys = false; this.escapeHtml = false; @@ -87,32 +88,32 @@ public GsonConfig() { addStep(createStep()); } - public GsonConfig indentWithSpaces(int indentSpaces) { + public GsonGradleConfig indentWithSpaces(int indentSpaces) { this.indentSpaces = indentSpaces; replaceStep(createStep()); return this; } - public GsonConfig sortByKeys() { + public GsonGradleConfig sortByKeys() { this.sortByKeys = true; replaceStep(createStep()); return this; } - public GsonConfig escapeHtml() { + public GsonGradleConfig escapeHtml() { this.escapeHtml = true; replaceStep(createStep()); return this; } - public GsonConfig version(String version) { + public GsonGradleConfig version(String version) { this.version = version; replaceStep(createStep()); return this; } private FormatterStep createStep() { - return GsonStep.create(indentSpaces, sortByKeys, escapeHtml, version, provisioner()); + return GsonStep.create(new GsonConfig(sortByKeys, escapeHtml, indentSpaces, version), provisioner()); } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Gson.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Gson.java index 7962ecb1f7..7dda88dcd9 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Gson.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Gson.java @@ -15,6 +15,8 @@ */ package com.diffplug.spotless.maven.json; +import com.diffplug.spotless.json.gson.GsonConfig; + import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.spotless.FormatterStep; @@ -23,7 +25,7 @@ import com.diffplug.spotless.maven.FormatterStepFactory; public class Gson implements FormatterStepFactory { - private static final String DEFAULT_GSON_VERSION = "2.8.9"; + private static final String DEFAULT_GSON_VERSION = "2.10.1"; @Parameter int indentSpaces = Json.DEFAULT_INDENTATION; @@ -40,6 +42,6 @@ public class Gson implements FormatterStepFactory { @Override public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { int indentSpaces = this.indentSpaces; - return GsonStep.create(indentSpaces, sortByKeys, escapeHtml, version, stepConfig.getProvisioner()); + return GsonStep.create(new GsonConfig(sortByKeys, escapeHtml, indentSpaces, version), stepConfig.getProvisioner()); } } From 0a86bad22192c5453cc08f8eebe3f28b44abd3d5 Mon Sep 17 00:00:00 2001 From: Abel Keszei Date: Thu, 19 Jan 2023 22:07:36 +0100 Subject: [PATCH 30/48] apply format --- .../spotless/glue/gson/GsonFormatterFunc.java | 37 +++++++++++++------ .../spotless/json/gson/GsonConfig.java | 15 ++++++++ .../diffplug/spotless/json/gson/GsonStep.java | 4 +- .../diffplug/spotless/maven/json/Gson.java | 3 +- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java b/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java index 33db1faaa6..c1426a06d1 100644 --- a/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java +++ b/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java @@ -1,19 +1,34 @@ +/* + * 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.gson; import java.io.IOException; import java.io.StringWriter; import java.util.Collections; -import com.diffplug.spotless.FormatterFunc; -import com.diffplug.spotless.ThrowingEx; -import com.diffplug.spotless.json.gson.GsonConfig; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.stream.JsonWriter; +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.ThrowingEx; +import com.diffplug.spotless.json.gson.GsonConfig; + public class GsonFormatterFunc implements FormatterFunc { private static final String FAILED_TO_PARSE_ERROR_MESSAGE = "Unable to format JSON"; @@ -60,13 +75,13 @@ public String apply(String inputString) { private JsonElement sortByKeys(JsonObject jsonObject) { JsonObject result = new JsonObject(); result.keySet().stream().sorted() - .forEach(key -> { - JsonElement element = jsonObject.get(key); - if (element.isJsonObject()) { - element = sortByKeys(element.getAsJsonObject()); - } - result.add(key, element); - }); + .forEach(key -> { + JsonElement element = jsonObject.get(key); + if (element.isJsonObject()) { + element = sortByKeys(element.getAsJsonObject()); + } + result.add(key, element); + }); return result; } diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonConfig.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonConfig.java index c63ad0aaba..a11c1ee296 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonConfig.java +++ b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonConfig.java @@ -1,3 +1,18 @@ +/* + * 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.json.gson; import java.io.Serializable; diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java index 624c4f2533..1fb0f11d62 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java +++ b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.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. @@ -46,7 +46,7 @@ private State(GsonConfig gsonConfig, Provisioner provisioner) throws IOException } FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, - InstantiationException, IllegalAccessException { + InstantiationException, IllegalAccessException { Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.gson.GsonFormatterFunc"); Constructor constructor = formatterFunc.getConstructor(GsonConfig.class); return (FormatterFunc) constructor.newInstance(gsonConfig); diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Gson.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Gson.java index 7dda88dcd9..fb5a38e890 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Gson.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Gson.java @@ -15,11 +15,10 @@ */ package com.diffplug.spotless.maven.json; -import com.diffplug.spotless.json.gson.GsonConfig; - import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.json.gson.GsonConfig; import com.diffplug.spotless.json.gson.GsonStep; import com.diffplug.spotless.maven.FormatterStepConfig; import com.diffplug.spotless.maven.FormatterStepFactory; From 290a522e154db4ce10116273b35139e5f64ea8bd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 21:43:58 +0000 Subject: [PATCH 31/48] Update plugin de.benediktritter.maven-plugin-development to v0.4.1 --- plugin-maven/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-maven/build.gradle b/plugin-maven/build.gradle index ad51318d93..664101a0fd 100644 --- a/plugin-maven/build.gradle +++ b/plugin-maven/build.gradle @@ -1,6 +1,6 @@ plugins { // https://www.benediktritter.de/maven-plugin-development/#release-history - id 'de.benediktritter.maven-plugin-development' version '0.4.0' + id 'de.benediktritter.maven-plugin-development' version '0.4.1' } repositories { mavenCentral() } From eea5d5c1d07f23c7381222bd5010b4933008dee9 Mon Sep 17 00:00:00 2001 From: Abel Keszei Date: Sun, 22 Jan 2023 14:36:44 +0100 Subject: [PATCH 32/48] fix tests --- .../spotless/glue/gson/GsonFormatterFunc.java | 2 +- .../com/diffplug/spotless/json/gson/GsonStep.java | 15 ++++++++++----- .../diffplug/spotless/json/gson/GsonStepTest.java | 14 +++++++------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java b/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java index c1426a06d1..b19476a1a8 100644 --- a/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java +++ b/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java @@ -74,7 +74,7 @@ public String apply(String inputString) { private JsonElement sortByKeys(JsonObject jsonObject) { JsonObject result = new JsonObject(); - result.keySet().stream().sorted() + jsonObject.keySet().stream().sorted() .forEach(key -> { JsonElement element = jsonObject.get(key); if (element.isJsonObject()) { diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java index 1fb0f11d62..58993017fa 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java +++ b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java @@ -28,6 +28,7 @@ public class GsonStep { private static final String MAVEN_COORDINATES = "com.google.code.gson:gson"; + private static final String INCOMPATIBLE_ERROR_MESSAGE = "There was a problem interacting with Gson; maybe you set an incompatible version?"; public static FormatterStep create(GsonConfig gsonConfig, Provisioner provisioner) { Objects.requireNonNull(provisioner, "provisioner cannot be null"); @@ -45,11 +46,15 @@ private State(GsonConfig gsonConfig, Provisioner provisioner) throws IOException this.jarState = JarState.from(MAVEN_COORDINATES + ":" + gsonConfig.getVersion(), provisioner); } - FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, - InstantiationException, IllegalAccessException { - Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.gson.GsonFormatterFunc"); - Constructor constructor = formatterFunc.getConstructor(GsonConfig.class); - return (FormatterFunc) constructor.newInstance(gsonConfig); + FormatterFunc toFormatter() { + try { + Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.gson.GsonFormatterFunc"); + Constructor constructor = formatterFunc.getConstructor(GsonConfig.class); + return (FormatterFunc) constructor.newInstance(gsonConfig); + } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException + | InstantiationException | IllegalAccessException | NoClassDefFoundError cause) { + throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); + } } } diff --git a/testlib/src/test/java/com/diffplug/spotless/json/gson/GsonStepTest.java b/testlib/src/test/java/com/diffplug/spotless/json/gson/GsonStepTest.java index 8f1d758836..3f4d2a84e0 100644 --- a/testlib/src/test/java/com/diffplug/spotless/json/gson/GsonStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/json/gson/GsonStepTest.java @@ -28,7 +28,7 @@ public class GsonStepTest extends JsonFormatterStepCommonTests { - private static final String DEFAULT_VERSION = "2.8.9"; + private static final String DEFAULT_VERSION = "2.10.1"; @Test void handlesComplexNestedObject() { @@ -52,34 +52,34 @@ void handlesNotJson() { @Test void handlesSortingWhenSortByKeyEnabled() { - FormatterStep step = GsonStep.create(INDENT, true, false, DEFAULT_VERSION, TestProvisioner.mavenCentral()); + FormatterStep step = GsonStep.create(new GsonConfig(true, false, INDENT, DEFAULT_VERSION), TestProvisioner.mavenCentral()); StepHarness.forStep(step).testResource("json/sortByKeysBefore.json", "json/sortByKeysAfter.json"); } @Test void doesNoSortingWhenSortByKeyDisabled() { - FormatterStep step = GsonStep.create(INDENT, false, false, DEFAULT_VERSION, TestProvisioner.mavenCentral()); + FormatterStep step = GsonStep.create(new GsonConfig(false, false, INDENT, DEFAULT_VERSION), TestProvisioner.mavenCentral()); StepHarness.forStep(step) .testResource("json/sortByKeysBefore.json", "json/sortByKeysAfterDisabled.json"); } @Test void handlesHtmlEscapeWhenEnabled() { - FormatterStep step = GsonStep.create(INDENT, false, true, DEFAULT_VERSION, TestProvisioner.mavenCentral()); + FormatterStep step = GsonStep.create(new GsonConfig(false, true, INDENT, DEFAULT_VERSION), TestProvisioner.mavenCentral()); StepHarness.forStep(step) .testResource("json/escapeHtmlGsonBefore.json", "json/escapeHtmlGsonAfter.json"); } @Test void writesRawHtmlWhenHtmlEscapeDisabled() { - FormatterStep step = GsonStep.create(INDENT, false, false, DEFAULT_VERSION, TestProvisioner.mavenCentral()); + FormatterStep step = GsonStep.create(new GsonConfig(false, false, INDENT, DEFAULT_VERSION), TestProvisioner.mavenCentral()); StepHarness.forStep(step) .testResource("json/escapeHtmlGsonBefore.json", "json/escapeHtmlGsonAfterDisabled.json"); } @Test void handlesVersionIncompatibility() { - FormatterStep step = GsonStep.create(INDENT, false, false, "1.7", TestProvisioner.mavenCentral()); + FormatterStep step = GsonStep.create(new GsonConfig(false, false, INDENT, "1.7"), TestProvisioner.mavenCentral()); Assertions.assertThatThrownBy(() -> step.format("", new File(""))) .isInstanceOf(IllegalStateException.class) .hasMessage("There was a problem interacting with Gson; maybe you set an incompatible version?"); @@ -87,6 +87,6 @@ void handlesVersionIncompatibility() { @Override protected FormatterStep createFormatterStep(int indent, Provisioner provisioner) { - return GsonStep.create(indent, false, false, DEFAULT_VERSION, provisioner); + return GsonStep.create(new GsonConfig(false, false, indent, DEFAULT_VERSION), provisioner); } } From f3481bee7c5f03ad450c2f7a875a204c594ef66f Mon Sep 17 00:00:00 2001 From: Abel Keszei Date: Sun, 22 Jan 2023 14:37:14 +0100 Subject: [PATCH 33/48] apply format --- lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java index 58993017fa..03baec5104 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java +++ b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java @@ -52,7 +52,7 @@ FormatterFunc toFormatter() { Constructor constructor = formatterFunc.getConstructor(GsonConfig.class); return (FormatterFunc) constructor.newInstance(gsonConfig); } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException - | InstantiationException | IllegalAccessException | NoClassDefFoundError cause) { + | InstantiationException | IllegalAccessException | NoClassDefFoundError cause) { throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); } } From 85d727e19a149fec2144e40fbe3c656ca83df0e2 Mon Sep 17 00:00:00 2001 From: Abel Keszei Date: Sun, 22 Jan 2023 14:55:19 +0100 Subject: [PATCH 34/48] update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 07b81a8fc5..32c7a7f5a5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494) ### Changes * Rename `YamlJacksonStep` into `JacksonYamlStep` while normalizing Jackson usage ([#1492](https://github.com/diffplug/spotless/pull/1492)) +* Convert `gson` integration to use a compile-only source set ([#1510](https://github.com/diffplug/spotless/pull/1510)). ## [2.32.0] - 2023-01-13 ### Added From bba246d883a3ad9847d75c4379b7e8b9be1f1907 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Fri, 20 Jan 2023 22:00:22 +0100 Subject: [PATCH 35/48] 1416: use process runner to catch output --- .../com/diffplug/spotless/ProcessRunner.java | 146 ++++++++++++++++-- .../com/diffplug/spotless/npm/NpmProcess.java | 34 ++-- 2 files changed, 153 insertions(+), 27 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java b/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java index 41c664cafe..4c076e537c 100644 --- a/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java +++ b/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java @@ -15,6 +15,8 @@ */ package com.diffplug.spotless; +import static java.util.Objects.requireNonNull; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -29,9 +31,12 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; -import edu.umd.cs.findbugs.annotations.Nullable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** @@ -95,6 +100,36 @@ public Result exec(@Nullable byte[] stdin, List args) throws IOException /** Creates a process with the given arguments, the given byte array is written to stdin immediately. */ public Result exec(@Nullable File cwd, @Nullable Map environment, @Nullable byte[] stdin, List args) throws IOException, InterruptedException { + LongRunningProcess process = start(cwd, environment, stdin, args); + try { + // wait for the process to finish + process.waitFor(); + // collect the output + return process.result(); + } catch (ExecutionException e) { + throw ThrowingEx.asRuntime(e); + } + } + + /** + * Creates a process with the given arguments, the given byte array is written to stdin immediately. + *
+ * Delegates to {@link #start(File, Map, byte[], boolean, List)} with {@code false} for {@code redirectErrorStream}. + */ + public LongRunningProcess start(@Nullable File cwd, @Nullable Map environment, @Nullable byte[] stdin, List args) throws IOException { + return start(cwd, environment, stdin, false, args); + } + + /** + * Creates a process with the given arguments, the given byte array is written to stdin immediately. + *
+ * The process is not waited for, so the caller is responsible for calling {@link LongRunningProcess#waitFor()} (if needed). + *
+ * To dispose this {@code ProcessRunner} instance, either call {@link #close()} or {@link LongRunningProcess#close()}. After + * {@link #close()} or {@link LongRunningProcess#close()} has been called, this {@code ProcessRunner} instance must not be used anymore. + */ + public LongRunningProcess start(@Nullable File cwd, @Nullable Map environment, @Nullable byte[] stdin, boolean redirectErrorStream, List args) throws IOException { + checkState(); ProcessBuilder builder = new ProcessBuilder(args); if (cwd != null) { builder.directory(cwd); @@ -105,20 +140,20 @@ public Result exec(@Nullable File cwd, @Nullable Map environment if (stdin == null) { stdin = new byte[0]; } + if (redirectErrorStream) { + builder.redirectErrorStream(true); + } + Process process = builder.start(); Future outputFut = threadStdOut.submit(() -> drainToBytes(process.getInputStream(), bufStdOut)); - Future errorFut = threadStdErr.submit(() -> drainToBytes(process.getErrorStream(), bufStdErr)); + Future errorFut = null; + if (!redirectErrorStream) { + errorFut = threadStdErr.submit(() -> drainToBytes(process.getErrorStream(), bufStdErr)); + } // write stdin process.getOutputStream().write(stdin); process.getOutputStream().close(); - // wait for the process to finish - int exitCode = process.waitFor(); - try { - // collect the output - return new Result(args, exitCode, outputFut.get(), errorFut.get()); - } catch (ExecutionException e) { - throw ThrowingEx.asRuntime(e); - } + return new LongRunningProcess(process, args, outputFut, errorFut); } private static void drain(InputStream input, OutputStream output) throws IOException { @@ -141,17 +176,24 @@ public void close() { threadStdErr.shutdown(); } + /** Checks if this {@code ProcessRunner} instance is still usable. */ + private void checkState() { + if (threadStdOut.isShutdown() || threadStdErr.isShutdown()) { + throw new IllegalStateException("ProcessRunner has been closed and must not be used anymore."); + } + } + @SuppressFBWarnings({"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) public static class Result { private final List args; private final int exitCode; private final byte[] stdOut, stdErr; - public Result(List args, int exitCode, byte[] stdOut, byte[] stdErr) { + public Result(@Nonnull List args, int exitCode, @Nonnull byte[] stdOut, @Nullable byte[] stdErr) { this.args = args; this.exitCode = exitCode; this.stdOut = stdOut; - this.stdErr = stdErr; + this.stdErr = (stdErr == null ? new byte[0] : stdErr); } public List args() { @@ -222,8 +264,86 @@ public String toString() { } }; perStream.accept(" stdout", stdOut); - perStream.accept(" stderr", stdErr); + if (stdErr.length > 0) { + perStream.accept(" stderr", stdErr); + } return builder.toString(); } } + + /** + * A long-running process that can be waited for. + */ + public class LongRunningProcess extends Process implements AutoCloseable { + + private final Process delegate; + private final List args; + private final Future outputFut; + private final Future errorFut; + + public LongRunningProcess(@Nonnull Process delegate, @Nonnull List args, @Nonnull Future outputFut, @Nullable Future errorFut) { + this.delegate = requireNonNull(delegate); + this.args = args; + this.outputFut = outputFut; + this.errorFut = errorFut; + } + + @Override + public OutputStream getOutputStream() { + return delegate.getOutputStream(); + } + + @Override + public InputStream getInputStream() { + return delegate.getInputStream(); + } + + @Override + public InputStream getErrorStream() { + return delegate.getErrorStream(); + } + + @Override + public int waitFor() throws InterruptedException { + return delegate.waitFor(); + } + + @Override + public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException { + return delegate.waitFor(timeout, unit); + } + + @Override + public int exitValue() { + return delegate.exitValue(); + } + + @Override + public void destroy() { + delegate.destroy(); + } + + @Override + public Process destroyForcibly() { + return delegate.destroyForcibly(); + } + + @Override + public boolean isAlive() { + return delegate.isAlive(); + } + + public Result result() throws ExecutionException, InterruptedException { + int exitCode = waitFor(); + return new Result(args, exitCode, this.outputFut.get(), (this.errorFut != null ? this.errorFut.get() : null)); + } + + @Override + public void close() { + if (isAlive()) { + destroy(); + } + ProcessRunner.this.close(); + } + } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java index 1675a4aa4e..b622cbd8ba 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java @@ -19,9 +19,15 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import com.diffplug.spotless.ProcessRunner; +import com.diffplug.spotless.ProcessRunner.LongRunningProcess; + class NpmProcess { private final File workingDir; @@ -30,10 +36,13 @@ class NpmProcess { private final File nodeExecutable; + private final ProcessRunner processRunner; + NpmProcess(File workingDir, File npmExecutable, File nodeExecutable) { this.workingDir = workingDir; this.npmExecutable = npmExecutable; this.nodeExecutable = nodeExecutable; + processRunner = new ProcessRunner(); } void install() { @@ -44,32 +53,27 @@ void install() { "--prefer-offline"); } - Process start() { + LongRunningProcess start() { // adding --scripts-prepend-node-path=true due to https://github.com/diffplug/spotless/issues/619#issuecomment-648018679 return npm("start", "--scripts-prepend-node-path=true"); } private void npmAwait(String... args) { - final Process npmProcess = npm(args); - - try { + try (LongRunningProcess npmProcess = npm(args)) { if (npmProcess.waitFor() != 0) { - throw new NpmProcessException("Running npm command '" + commandLine(args) + "' failed with exit code: " + npmProcess.exitValue()); + throw new NpmProcessException("Running npm command '" + commandLine(args) + "' failed with exit code: " + npmProcess.exitValue() + "\n\n" + npmProcess.result()); } } catch (InterruptedException e) { throw new NpmProcessException("Running npm command '" + commandLine(args) + "' was interrupted.", e); + } catch (ExecutionException e) { + throw new NpmProcessException("Running npm command '" + commandLine(args) + "' failed.", e); } } - private Process npm(String... args) { + private LongRunningProcess npm(String... args) { List processCommand = processCommand(args); try { - ProcessBuilder processBuilder = new ProcessBuilder() - .inheritIO() - .directory(this.workingDir) - .command(processCommand); - addEnvironmentVariables(processBuilder); - return processBuilder.start(); + return processRunner.start(this.workingDir, environmentVariables(), null, true, processCommand); } catch (IOException e) { throw new NpmProcessException("Failed to launch npm command '" + commandLine(args) + "'.", e); } @@ -82,8 +86,10 @@ private List processCommand(String... args) { return command; } - private void addEnvironmentVariables(ProcessBuilder processBuilder) { - processBuilder.environment().put("PATH", this.nodeExecutable.getParentFile().getAbsolutePath() + File.pathSeparator + System.getenv("PATH")); + private Map environmentVariables() { + Map environmentVariables = new HashMap<>(); + environmentVariables.put("PATH", this.nodeExecutable.getParentFile().getAbsolutePath() + File.pathSeparator + System.getenv("PATH")); + return environmentVariables; } private String commandLine(String... args) { From 1c285ecfcd321c0ba37c2ca5d993566e2ce15398 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Fri, 20 Jan 2023 23:04:26 +0100 Subject: [PATCH 36/48] 1416: suggest prettier plugins --- .../spotless/npm/PrettierFormatterStep.java | 10 +- .../npm/PrettierMissingParserException.java | 116 ++++++++++++++++++ .../spotless/PrettierIntegrationTest.java | 23 ++++ 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/npm/PrettierMissingParserException.java 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 20ff71d08e..22dd4586ee 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -122,7 +122,14 @@ 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 + "'"); final String prettierConfigOptionsWithFilepath = assertFilepathInConfigOptions(file); - return restService.format(unix, prettierConfigOptionsWithFilepath); + try { + return restService.format(unix, prettierConfigOptionsWithFilepath); + } catch (SimpleRestClient.SimpleRestResponseException e) { + if (e.getStatusCode() != 200 && e.getResponseMessage().contains("No parser could be inferred")) { + throw new PrettierMissingParserException(file, e); + } + throw e; + } } private String assertFilepathInConfigOptions(File file) { @@ -141,4 +148,5 @@ private String assertFilepathInConfigOptions(File file) { return "{" + filePathOption + (hasAnyConfigOption ? "," : "") + prettierConfigOptions.substring(startOfConfigOption + 1); } } + } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierMissingParserException.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierMissingParserException.java new file mode 100644 index 0000000000..73f81a0e07 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierMissingParserException.java @@ -0,0 +1,116 @@ +/* + * 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.npm; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import javax.annotation.Nonnull; + +class PrettierMissingParserException extends RuntimeException { + private static final long serialVersionUID = 1L; + + private static final Map EXTENSIONS_TO_PLUGINS; + + static { + Map plugins = new HashMap<>(); + // ---- official plugins + plugins.put(".php", "@prettier/plugin-php"); + plugins.put(".pug", "@prettier/plugin-pug"); + plugins.put(".rb", "@prettier/plugin-ruby"); + plugins.put(".xml", "@prettier/plugin-xml"); + + // ---- community plugins + // default namings: astro, elm, java, jsonata, prisma, properties, sh, sql, svelte, toml + plugins.put(".trigger", "prettier-plugin-apex"); + plugins.put(".cls", "prettier-plugin-apex"); + plugins.put(".html.erb", "prettier-plugin-erb"); + Arrays.asList(".glsl", + ".fp", + ".frag", + ".frg", + ".fs", + ".fsh", + ".fshader", + ".geo", + ".geom", + ".glslf", + ".glslv", + ".gs", + ".gshader", + ".rchit", + ".rmiss", + ".shader", + ".tesc", + ".tese", + ".vert", + ".vrx", + ".vsh", + ".vshader").forEach(ext -> plugins.put(ext, "prettier-plugin-glsl")); + Arrays.asList(".go.html", + ".gohtml", + ".gotmpl", + ".go.tmpl", + ".tmpl", + ".tpl", + ".html.tmpl", + ".html.tpl").forEach(ext -> plugins.put(ext, "prettier-plugin-go-template")); + plugins.put(".kt", "kotlin"); + plugins.put(".mo", "motoko"); + Arrays.asList(".nginx", ".nginxconf").forEach(ext -> plugins.put(ext, "prettier-plugin-nginx")); + plugins.put(".sol", "prettier-plugin-solidity"); + + EXTENSIONS_TO_PLUGINS = Collections.unmodifiableMap(plugins); + } + + private final File file; + + public PrettierMissingParserException(@Nonnull File file, Exception cause) { + super("Prettier could not infer a parser for file '" + file + "'. Maybe you need to include a prettier plugin in devDependencies?\n\n" + recommendPlugin(file), cause); + this.file = Objects.requireNonNull(file); + } + + private static String recommendPlugin(File file) { + String pluginName = guessPlugin(file); + return "A good candidate for file '" + file + "' is '" + pluginName + "\n" + + "See if you can find it on \n" + + "or search on npmjs.com for a plugin matching that name: " + + String.format("\n\n", pluginName) + + "For instructions on how to include plugins for prettier in spotless see our documentation:\n" + + "- for gradle \n" + + "- for maven "; + } + + private static String guessPlugin(File file) { + return EXTENSIONS_TO_PLUGINS.entrySet().stream() + .filter(entry -> file.getName().endsWith(entry.getKey())) + .findFirst() + .map(entry -> entry.getValue()) + .orElse("prettier-plugin-" + extension(file)); + } + + public String fileType() { + return extension(file); + } + + private static String extension(File file) { + return file.getName().substring(file.getName().lastIndexOf('.') + 1); + } +} 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 bc8ad1148e..4c458e3ca7 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 @@ -135,6 +135,29 @@ void useJavaCommunityPlugin() throws IOException { assertFile("JavaTest.java").sameAsResource("npm/prettier/plugins/java-test.clean"); } + @Test + void suggestsMissingJavaCommunityPlugin() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "def prettierConfig = [:]", + "prettierConfig['tabWidth'] = 4", + "def prettierPackages = [:]", + "prettierPackages['prettier'] = '2.0.5'", + "spotless {", + " format 'java', {", + " target 'JavaTest.java'", + " prettier(prettierPackages).config(prettierConfig)", + " }", + "}"); + setFile("JavaTest.java").toResource("npm/prettier/plugins/java-test.dirty"); + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").buildAndFail(); + Assertions.assertThat(spotlessApply.getOutput()).contains("could not infer a parser"); + Assertions.assertThat(spotlessApply.getOutput()).contains("prettier-plugin-java"); + } + @Test void usePhpCommunityPlugin() throws IOException { setFile("build.gradle").toLines( From 32eee78c9ed3dfc542213f5a72fdfde2503c005c Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 22 Jan 2023 21:15:32 +0100 Subject: [PATCH 37/48] 1416: limit output fetched by npm process usually only the last few lines are of interest anyway, so be kind to memory usage. --- ...mitedOverwritingByteArrayOutputStream.java | 132 +++++++++++++ .../com/diffplug/spotless/ProcessRunner.java | 13 +- .../com/diffplug/spotless/npm/NpmProcess.java | 2 +- ...dOverwritingByteArrayOutputStreamTest.java | 179 ++++++++++++++++++ 4 files changed, 322 insertions(+), 4 deletions(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStream.java create mode 100644 lib/src/test/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStreamTest.java diff --git a/lib/src/main/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStream.java b/lib/src/main/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStream.java new file mode 100644 index 0000000000..a76039fccd --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStream.java @@ -0,0 +1,132 @@ +/* + * 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.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +public class LimitedOverwritingByteArrayOutputStream extends ByteArrayOutputStream { + + private final int limit; + + private int zeroIndexPointer = 0; + + private boolean isOverLimit = false; + + public LimitedOverwritingByteArrayOutputStream(int limit) { + this(limit, 32); + } + + public LimitedOverwritingByteArrayOutputStream(int limit, int initialCapacity) { + super(initialCapacity); + if (limit < initialCapacity) { + throw new IllegalArgumentException("Limit must be greater than initial capacity"); + } + if (limit < 0) { + throw new IllegalArgumentException("Limit must be greater than 0"); + } + if (limit % 2 != 0) { + throw new IllegalArgumentException("Limit must be even"); // to fit 16 bit unicode chars + } + this.limit = limit; + } + + // ---- writing + @Override + public synchronized void write(int b) { + if (count < limit) { + super.write(b); + return; + } + isOverLimit = true; + buf[zeroIndexPointer] = (byte) b; + zeroIndexPointer = (zeroIndexPointer + 1) % limit; + } + + @Override + public synchronized void write(byte[] b, int off, int len) { + int remaining = limit - count; + if (remaining >= len) { + super.write(b, off, len); + return; + } + if (remaining > 0) { + // write what we can "normally" + super.write(b, off, remaining); + // rest delegated + write(b, off + remaining, len - remaining); + return; + } + // we are over the limit + isOverLimit = true; + // write till limit is reached + int writeTillLimit = Math.min(len, limit - zeroIndexPointer); + System.arraycopy(b, off, buf, zeroIndexPointer, writeTillLimit); + zeroIndexPointer = (zeroIndexPointer + writeTillLimit) % limit; + if (writeTillLimit < len) { + // write rest + write(b, off + writeTillLimit, len - writeTillLimit); + } + } + + @Override + public synchronized void reset() { + super.reset(); + zeroIndexPointer = 0; + isOverLimit = false; + } + + // ---- output + @Override + public synchronized void writeTo(OutputStream out) throws IOException { + if (!isOverLimit) { + super.writeTo(out); + return; + } + out.write(buf, zeroIndexPointer, limit - zeroIndexPointer); + out.write(buf, 0, zeroIndexPointer); + } + + @Override + public synchronized byte[] toByteArray() { + if (!isOverLimit) { + return super.toByteArray(); + } + byte[] result = new byte[limit]; + System.arraycopy(buf, zeroIndexPointer, result, 0, limit - zeroIndexPointer); + System.arraycopy(buf, 0, result, limit - zeroIndexPointer, zeroIndexPointer); + return result; + } + + @Override + public synchronized String toString() { + if (!isOverLimit) { + return super.toString(); + } + return new String(buf, zeroIndexPointer, limit - zeroIndexPointer) + new String(buf, 0, zeroIndexPointer); + } + + @Override + public synchronized String toString(String charsetName) throws UnsupportedEncodingException { + if (!isOverLimit) { + return super.toString(charsetName); + } + return new String(buf, zeroIndexPointer, limit - zeroIndexPointer, charsetName) + new String(buf, 0, zeroIndexPointer, charsetName); + } + +} diff --git a/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java b/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java index 4c076e537c..0979c4cbd4 100644 --- a/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java +++ b/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java @@ -52,10 +52,17 @@ public class ProcessRunner implements AutoCloseable { private final ExecutorService threadStdOut = Executors.newSingleThreadExecutor(); private final ExecutorService threadStdErr = Executors.newSingleThreadExecutor(); - private final ByteArrayOutputStream bufStdOut = new ByteArrayOutputStream(); - private final ByteArrayOutputStream bufStdErr = new ByteArrayOutputStream(); + private final ByteArrayOutputStream bufStdOut; + private final ByteArrayOutputStream bufStdErr; - public ProcessRunner() {} + public ProcessRunner() { + this(-1); + } + + public ProcessRunner(int limitedBuffers) { + this.bufStdOut = limitedBuffers >= 0 ? new LimitedOverwritingByteArrayOutputStream(limitedBuffers) : new ByteArrayOutputStream(); + this.bufStdErr = limitedBuffers >= 0 ? new LimitedOverwritingByteArrayOutputStream(limitedBuffers) : new ByteArrayOutputStream(); + } /** Executes the given shell command (using {@code cmd} on windows and {@code sh} on unix). */ public Result shell(String cmd) throws IOException, InterruptedException { diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java index b622cbd8ba..f97193745d 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java @@ -42,7 +42,7 @@ class NpmProcess { this.workingDir = workingDir; this.npmExecutable = npmExecutable; this.nodeExecutable = nodeExecutable; - processRunner = new ProcessRunner(); + processRunner = new ProcessRunner(100 * 1024); // 100kB } void install() { diff --git a/lib/src/test/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStreamTest.java b/lib/src/test/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStreamTest.java new file mode 100644 index 0000000000..63998a4d75 --- /dev/null +++ b/lib/src/test/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStreamTest.java @@ -0,0 +1,179 @@ +/* + * 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.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.stream.Stream; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class LimitedOverwritingByteArrayOutputStreamTest { + + private final byte[] bytes = new byte[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void toStringBehavesNormallyWithinLimit(String name, ByteWriteStrategy writeStrategy) { + LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(12, 1); + writeStrategy.write(stream, bytes); + Assertions.assertThat(stream.toString()).isEqualTo("0123456789"); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void toStringBehavesOverwritingOverLimit(String name, ByteWriteStrategy writeStrategy) { + LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(4, 1); + writeStrategy.write(stream, bytes); + Assertions.assertThat(stream.toString()).hasSize(4); + Assertions.assertThat(stream.toString()).isEqualTo("6789"); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void toStringBehavesNormallyAtExactlyLimit(String name, ByteWriteStrategy writeStrategy) { + LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(bytes.length, 1); + writeStrategy.write(stream, bytes); + Assertions.assertThat(stream.toString()).isEqualTo("0123456789"); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void toByteArrayBehavesNormallyWithinLimit(String name, ByteWriteStrategy writeStrategy) { + LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(12, 1); + writeStrategy.write(stream, bytes); + Assertions.assertThat(stream.toByteArray()).isEqualTo(bytes); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void toByteArrayBehavesOverwritingOverLimit(String name, ByteWriteStrategy writeStrategy) { + LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(4, 1); + writeStrategy.write(stream, bytes); + Assertions.assertThat(stream.toByteArray()).hasSize(4); + Assertions.assertThat(stream.toByteArray()).isEqualTo(new byte[]{'6', '7', '8', '9'}); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void toByteArrayBehavesOverwritingAtExactlyLimit(String name, ByteWriteStrategy writeStrategy) { + LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(bytes.length, 1); + writeStrategy.write(stream, bytes); + Assertions.assertThat(stream.toByteArray()).isEqualTo(bytes); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void writeToBehavesNormallyWithinLimit(String name, ByteWriteStrategy writeStrategy) throws IOException { + LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(12, 1); + writeStrategy.write(stream, bytes); + ByteArrayOutputStream target = new ByteArrayOutputStream(); + stream.writeTo(target); + Assertions.assertThat(target.toByteArray()).isEqualTo(bytes); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void writeToBehavesOverwritingOverLimit(String name, ByteWriteStrategy writeStrategy) throws IOException { + LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(4, 1); + writeStrategy.write(stream, bytes); + ByteArrayOutputStream target = new ByteArrayOutputStream(); + stream.writeTo(target); + Assertions.assertThat(target.toByteArray()).hasSize(4); + Assertions.assertThat(target.toByteArray()).isEqualTo(new byte[]{'6', '7', '8', '9'}); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void writeToBehavesNormallyAtExactlyLimit(String name, ByteWriteStrategy writeStrategy) throws IOException { + LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(bytes.length, 1); + writeStrategy.write(stream, bytes); + ByteArrayOutputStream target = new ByteArrayOutputStream(); + stream.writeTo(target); + Assertions.assertThat(target.toByteArray()).isEqualTo(bytes); + } + + @Test + void writeToBehavesCorrectlyWhenOverLimitMultipleCalls() { + // this test explicitly captures a border case where the buffer is not empty but can exactly fit what we are writing + LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(2, 1); + stream.write('0'); + stream.write(new byte[]{'1', '2'}, 0, 2); + Assertions.assertThat(stream.toString()).hasSize(2); + Assertions.assertThat(stream.toString()).isEqualTo("12"); + } + + private static Stream writeStrategies() { + return Stream.of( + Arguments.of("writeAllAtOnce", allAtOnce()), + Arguments.of("writeOneByteAtATime", oneByteAtATime()), + Arguments.of("writeTwoBytesAtATime", twoBytesAtATime()), + Arguments.of("writeOneAndThenTwoBytesAtATime", oneAndThenTwoBytesAtATime()), + Arguments.of("firstFourBytesAndThenTheRest", firstFourBytesAndThenTheRest())); + } + + private static ByteWriteStrategy allAtOnce() { + return (stream, bytes) -> stream.write(bytes, 0, bytes.length); + } + + private static ByteWriteStrategy oneByteAtATime() { + return (stream, bytes) -> { + for (byte b : bytes) { + stream.write(b); + } + }; + } + + private static ByteWriteStrategy twoBytesAtATime() { + return (stream, bytes) -> { + for (int i = 0; i < bytes.length; i += 2) { + stream.write(bytes, i, 2); + } + }; + } + + private static ByteWriteStrategy oneAndThenTwoBytesAtATime() { + return (stream, bytes) -> { + int written = 0; + for (int i = 0; i + 3 < bytes.length; i += 3) { + stream.write(bytes, i, 1); + stream.write(bytes, i + 1, 2); + written += 3; + } + if (written < bytes.length) { + stream.write(bytes, written, bytes.length - written); + } + + }; + } + + private static ByteWriteStrategy firstFourBytesAndThenTheRest() { + return (stream, bytes) -> { + stream.write(bytes, 0, 4); + stream.write(bytes, 4, bytes.length - 4); + }; + } + + @FunctionalInterface + private interface ByteWriteStrategy { + void write(LimitedOverwritingByteArrayOutputStream stream, byte[] bytes); + } + +} From 8b56d5dafa4af82b6c327121df14ad0612e6494d Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 22 Jan 2023 21:24:24 +0100 Subject: [PATCH 38/48] 1416: document changes --- CHANGES.md | 2 ++ plugin-gradle/CHANGES.md | 2 ++ plugin-maven/CHANGES.md | 2 ++ 3 files changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 07b81a8fc5..f5882e5d99 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added * `ProcessRunner` has added some convenience methods so it can be used for maven testing. ([#1496](https://github.com/diffplug/spotless/pull/1496)) +* `ProcessRunner` allows to limit captured output to a certain number of bytes. ([#1511](https://github.com/diffplug/spotless/pull/1511)) +* `ProcessRunner` is now capable of handling long-running tasks where waiting for exit is delegated to the caller. ([#1511](https://github.com/diffplug/spotless/pull/1511)) * Allow to specify node executable for node-based formatters using `nodeExecutable` parameter ([#1500](https://github.com/diffplug/spotless/pull/1500)) ### Fixed * The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494) diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index cac247a307..f8a6242adf 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -10,6 +10,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ### Fixed * The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494) ### Changes +* Prettier will now suggest to install plugins if a parser cannot be inferred from the file extension ([#1511](https://github.com/diffplug/spotless/pull/1511)) + ## [6.13.0] - 2023-01-14 ### Added diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index ecb67f231f..f1ab739b62 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -13,6 +13,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494) ### Changes * Spotless' custom build was replaced by [`maven-plugin-development`](https://github.com/britter/maven-plugin-development). ([#1496](https://github.com/diffplug/spotless/pull/1496) fixes [#554](https://github.com/diffplug/spotless/issues/554)) +* Prettier will now suggest to install plugins if a parser cannot be inferred from the file extension ([#1511](https://github.com/diffplug/spotless/pull/1511)) + ## [2.30.0] - 2023-01-13 ### Added From 2c54ee976245a486572c1b728b5f1dd8db1d06e3 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Mon, 23 Jan 2023 08:59:28 +0100 Subject: [PATCH 39/48] 1416: Integrate PR Feedback --- .../com/diffplug/spotless/ProcessRunner.java | 10 +++++--- ...a => RingBufferByteArrayOutputStream.java} | 14 +++++------ .../com/diffplug/spotless/npm/NpmProcess.java | 2 +- ... RingBufferByteArrayOutputStreamTest.java} | 24 +++++++++---------- 4 files changed, 27 insertions(+), 23 deletions(-) rename lib/src/main/java/com/diffplug/spotless/{LimitedOverwritingByteArrayOutputStream.java => RingBufferByteArrayOutputStream.java} (88%) rename lib/src/test/java/com/diffplug/spotless/{LimitedOverwritingByteArrayOutputStreamTest.java => RingBufferByteArrayOutputStreamTest.java} (83%) diff --git a/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java b/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java index 0979c4cbd4..4e48042184 100644 --- a/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java +++ b/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java @@ -59,9 +59,13 @@ public ProcessRunner() { this(-1); } - public ProcessRunner(int limitedBuffers) { - this.bufStdOut = limitedBuffers >= 0 ? new LimitedOverwritingByteArrayOutputStream(limitedBuffers) : new ByteArrayOutputStream(); - this.bufStdErr = limitedBuffers >= 0 ? new LimitedOverwritingByteArrayOutputStream(limitedBuffers) : new ByteArrayOutputStream(); + public static ProcessRunner usingRingBuffersOfCapacity(int limit) { + return new ProcessRunner(limit); + } + + private ProcessRunner(int limitedBuffers) { + this.bufStdOut = limitedBuffers >= 0 ? new RingBufferByteArrayOutputStream(limitedBuffers) : new ByteArrayOutputStream(); + this.bufStdErr = limitedBuffers >= 0 ? new RingBufferByteArrayOutputStream(limitedBuffers) : new ByteArrayOutputStream(); } /** Executes the given shell command (using {@code cmd} on windows and {@code sh} on unix). */ diff --git a/lib/src/main/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStream.java b/lib/src/main/java/com/diffplug/spotless/RingBufferByteArrayOutputStream.java similarity index 88% rename from lib/src/main/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStream.java rename to lib/src/main/java/com/diffplug/spotless/RingBufferByteArrayOutputStream.java index a76039fccd..0353b50846 100644 --- a/lib/src/main/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStream.java +++ b/lib/src/main/java/com/diffplug/spotless/RingBufferByteArrayOutputStream.java @@ -20,7 +20,7 @@ import java.io.OutputStream; import java.io.UnsupportedEncodingException; -public class LimitedOverwritingByteArrayOutputStream extends ByteArrayOutputStream { +class RingBufferByteArrayOutputStream extends ByteArrayOutputStream { private final int limit; @@ -28,20 +28,20 @@ public class LimitedOverwritingByteArrayOutputStream extends ByteArrayOutputStre private boolean isOverLimit = false; - public LimitedOverwritingByteArrayOutputStream(int limit) { + public RingBufferByteArrayOutputStream(int limit) { this(limit, 32); } - public LimitedOverwritingByteArrayOutputStream(int limit, int initialCapacity) { + public RingBufferByteArrayOutputStream(int limit, int initialCapacity) { super(initialCapacity); if (limit < initialCapacity) { - throw new IllegalArgumentException("Limit must be greater than initial capacity"); + throw new IllegalArgumentException("Limit must be greater than initial capacity. Limit: " + limit + ", initial capacity: " + initialCapacity); } - if (limit < 0) { - throw new IllegalArgumentException("Limit must be greater than 0"); + if (limit < 2) { + throw new IllegalArgumentException("Limit must be greater than or equal to 2 but is " + limit); } if (limit % 2 != 0) { - throw new IllegalArgumentException("Limit must be even"); // to fit 16 bit unicode chars + throw new IllegalArgumentException("Limit must be an even number but is " + limit); // to fit 16 bit unicode chars } this.limit = limit; } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java index f97193745d..6384900d82 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java @@ -42,7 +42,7 @@ class NpmProcess { this.workingDir = workingDir; this.npmExecutable = npmExecutable; this.nodeExecutable = nodeExecutable; - processRunner = new ProcessRunner(100 * 1024); // 100kB + processRunner = ProcessRunner.usingRingBuffersOfCapacity(100 * 1024); // 100kB } void install() { diff --git a/lib/src/test/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStreamTest.java b/lib/src/test/java/com/diffplug/spotless/RingBufferByteArrayOutputStreamTest.java similarity index 83% rename from lib/src/test/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStreamTest.java rename to lib/src/test/java/com/diffplug/spotless/RingBufferByteArrayOutputStreamTest.java index 63998a4d75..94fa49dbc1 100644 --- a/lib/src/test/java/com/diffplug/spotless/LimitedOverwritingByteArrayOutputStreamTest.java +++ b/lib/src/test/java/com/diffplug/spotless/RingBufferByteArrayOutputStreamTest.java @@ -25,14 +25,14 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -class LimitedOverwritingByteArrayOutputStreamTest { +class RingBufferByteArrayOutputStreamTest { private final byte[] bytes = new byte[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; @ParameterizedTest(name = "{index} writeStrategy: {0}") @MethodSource("writeStrategies") void toStringBehavesNormallyWithinLimit(String name, ByteWriteStrategy writeStrategy) { - LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(12, 1); + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(12, 1); writeStrategy.write(stream, bytes); Assertions.assertThat(stream.toString()).isEqualTo("0123456789"); } @@ -40,7 +40,7 @@ void toStringBehavesNormallyWithinLimit(String name, ByteWriteStrategy writeStra @ParameterizedTest(name = "{index} writeStrategy: {0}") @MethodSource("writeStrategies") void toStringBehavesOverwritingOverLimit(String name, ByteWriteStrategy writeStrategy) { - LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(4, 1); + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(4, 1); writeStrategy.write(stream, bytes); Assertions.assertThat(stream.toString()).hasSize(4); Assertions.assertThat(stream.toString()).isEqualTo("6789"); @@ -49,7 +49,7 @@ void toStringBehavesOverwritingOverLimit(String name, ByteWriteStrategy writeStr @ParameterizedTest(name = "{index} writeStrategy: {0}") @MethodSource("writeStrategies") void toStringBehavesNormallyAtExactlyLimit(String name, ByteWriteStrategy writeStrategy) { - LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(bytes.length, 1); + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(bytes.length, 1); writeStrategy.write(stream, bytes); Assertions.assertThat(stream.toString()).isEqualTo("0123456789"); } @@ -57,7 +57,7 @@ void toStringBehavesNormallyAtExactlyLimit(String name, ByteWriteStrategy writeS @ParameterizedTest(name = "{index} writeStrategy: {0}") @MethodSource("writeStrategies") void toByteArrayBehavesNormallyWithinLimit(String name, ByteWriteStrategy writeStrategy) { - LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(12, 1); + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(12, 1); writeStrategy.write(stream, bytes); Assertions.assertThat(stream.toByteArray()).isEqualTo(bytes); } @@ -65,7 +65,7 @@ void toByteArrayBehavesNormallyWithinLimit(String name, ByteWriteStrategy writeS @ParameterizedTest(name = "{index} writeStrategy: {0}") @MethodSource("writeStrategies") void toByteArrayBehavesOverwritingOverLimit(String name, ByteWriteStrategy writeStrategy) { - LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(4, 1); + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(4, 1); writeStrategy.write(stream, bytes); Assertions.assertThat(stream.toByteArray()).hasSize(4); Assertions.assertThat(stream.toByteArray()).isEqualTo(new byte[]{'6', '7', '8', '9'}); @@ -74,7 +74,7 @@ void toByteArrayBehavesOverwritingOverLimit(String name, ByteWriteStrategy write @ParameterizedTest(name = "{index} writeStrategy: {0}") @MethodSource("writeStrategies") void toByteArrayBehavesOverwritingAtExactlyLimit(String name, ByteWriteStrategy writeStrategy) { - LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(bytes.length, 1); + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(bytes.length, 1); writeStrategy.write(stream, bytes); Assertions.assertThat(stream.toByteArray()).isEqualTo(bytes); } @@ -82,7 +82,7 @@ void toByteArrayBehavesOverwritingAtExactlyLimit(String name, ByteWriteStrategy @ParameterizedTest(name = "{index} writeStrategy: {0}") @MethodSource("writeStrategies") void writeToBehavesNormallyWithinLimit(String name, ByteWriteStrategy writeStrategy) throws IOException { - LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(12, 1); + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(12, 1); writeStrategy.write(stream, bytes); ByteArrayOutputStream target = new ByteArrayOutputStream(); stream.writeTo(target); @@ -92,7 +92,7 @@ void writeToBehavesNormallyWithinLimit(String name, ByteWriteStrategy writeStrat @ParameterizedTest(name = "{index} writeStrategy: {0}") @MethodSource("writeStrategies") void writeToBehavesOverwritingOverLimit(String name, ByteWriteStrategy writeStrategy) throws IOException { - LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(4, 1); + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(4, 1); writeStrategy.write(stream, bytes); ByteArrayOutputStream target = new ByteArrayOutputStream(); stream.writeTo(target); @@ -103,7 +103,7 @@ void writeToBehavesOverwritingOverLimit(String name, ByteWriteStrategy writeStra @ParameterizedTest(name = "{index} writeStrategy: {0}") @MethodSource("writeStrategies") void writeToBehavesNormallyAtExactlyLimit(String name, ByteWriteStrategy writeStrategy) throws IOException { - LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(bytes.length, 1); + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(bytes.length, 1); writeStrategy.write(stream, bytes); ByteArrayOutputStream target = new ByteArrayOutputStream(); stream.writeTo(target); @@ -113,7 +113,7 @@ void writeToBehavesNormallyAtExactlyLimit(String name, ByteWriteStrategy writeSt @Test void writeToBehavesCorrectlyWhenOverLimitMultipleCalls() { // this test explicitly captures a border case where the buffer is not empty but can exactly fit what we are writing - LimitedOverwritingByteArrayOutputStream stream = new LimitedOverwritingByteArrayOutputStream(2, 1); + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(2, 1); stream.write('0'); stream.write(new byte[]{'1', '2'}, 0, 2); Assertions.assertThat(stream.toString()).hasSize(2); @@ -173,7 +173,7 @@ private static ByteWriteStrategy firstFourBytesAndThenTheRest() { @FunctionalInterface private interface ByteWriteStrategy { - void write(LimitedOverwritingByteArrayOutputStream stream, byte[] bytes); + void write(RingBufferByteArrayOutputStream stream, byte[] bytes); } } From c0cd99ddc2cdf630423cc4c38b7a0b97dec225ea Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Mon, 23 Jan 2023 09:10:30 +0100 Subject: [PATCH 40/48] 1416: spotbugs adaptions --- .../com/diffplug/spotless/RingBufferByteArrayOutputStream.java | 3 +++ .../diffplug/spotless/npm/PrettierMissingParserException.java | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/com/diffplug/spotless/RingBufferByteArrayOutputStream.java b/lib/src/main/java/com/diffplug/spotless/RingBufferByteArrayOutputStream.java index 0353b50846..da4fc6aa04 100644 --- a/lib/src/main/java/com/diffplug/spotless/RingBufferByteArrayOutputStream.java +++ b/lib/src/main/java/com/diffplug/spotless/RingBufferByteArrayOutputStream.java @@ -20,6 +20,8 @@ import java.io.OutputStream; import java.io.UnsupportedEncodingException; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + class RingBufferByteArrayOutputStream extends ByteArrayOutputStream { private final int limit; @@ -113,6 +115,7 @@ public synchronized byte[] toByteArray() { return result; } + @SuppressFBWarnings(value = "DM_DEFAULT_ENCODING", justification = "We want to use the default encoding here since this is contract on ByteArrayOutputStream") @Override public synchronized String toString() { if (!isOverLimit) { diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierMissingParserException.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierMissingParserException.java index 73f81a0e07..6956545135 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierMissingParserException.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierMissingParserException.java @@ -92,7 +92,8 @@ private static String recommendPlugin(File file) { return "A good candidate for file '" + file + "' is '" + pluginName + "\n" + "See if you can find it on \n" + "or search on npmjs.com for a plugin matching that name: " - + String.format("\n\n", pluginName) + + String.format("", pluginName) + + "\n\n" + "For instructions on how to include plugins for prettier in spotless see our documentation:\n" + "- for gradle \n" + "- for maven "; From 4aa3b7e0f460eaa7a7c329a7868e71060151c95b Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 23 Jan 2023 12:13:01 -0800 Subject: [PATCH 41/48] Run spotbugs in all configurations because it's not that slow (17 seconds on my M1) and it's confusing to PR authors to not run. --- gradle/java-setup.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/gradle/java-setup.gradle b/gradle/java-setup.gradle index 9c9f394405..f8f2dedf39 100644 --- a/gradle/java-setup.gradle +++ b/gradle/java-setup.gradle @@ -34,8 +34,6 @@ tasks.withType(com.github.spotbugs.snom.SpotBugsTask).configureEach { } tasks.named('spotbugsMain') { - // only run on Java 8 (no benefit to running twice) - enabled = org.gradle.api.JavaVersion.current() == org.gradle.api.JavaVersion.VERSION_11 reports { html.enabled = true } From c849de8a376989005235263e5172fb7141218499 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 23 Jan 2023 15:59:14 -0800 Subject: [PATCH 42/48] Revert previous commit b/c spotbugs gives errant warnings on Java 8. --- gradle/java-setup.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gradle/java-setup.gradle b/gradle/java-setup.gradle index f8f2dedf39..9c9f394405 100644 --- a/gradle/java-setup.gradle +++ b/gradle/java-setup.gradle @@ -34,6 +34,8 @@ tasks.withType(com.github.spotbugs.snom.SpotBugsTask).configureEach { } tasks.named('spotbugsMain') { + // only run on Java 8 (no benefit to running twice) + enabled = org.gradle.api.JavaVersion.current() == org.gradle.api.JavaVersion.VERSION_11 reports { html.enabled = true } From 5955883bf5fb7bb77f8a970725040a74e8b56205 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 24 Jan 2023 16:12:13 +0800 Subject: [PATCH 43/48] Defer more test configurations --- _ext/eclipse-wtp/build.gradle | 2 +- _ext/gradle/java-setup.gradle | 4 +++- gradle/special-tests.gradle | 2 +- lib-extra/build.gradle | 2 +- plugin-gradle/build.gradle | 2 +- testlib/build.gradle | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/_ext/eclipse-wtp/build.gradle b/_ext/eclipse-wtp/build.gradle index 92a78578f6..feacfbf4c1 100644 --- a/_ext/eclipse-wtp/build.gradle +++ b/_ext/eclipse-wtp/build.gradle @@ -100,7 +100,7 @@ sourceSets { * All test classes need to run separately since they all instatiate different setups of the * Eclipse framework. */ -test { +tasks.withType(Test).configureEach { //Skip default tests, which would run every test case. exclude '**' } diff --git a/_ext/gradle/java-setup.gradle b/_ext/gradle/java-setup.gradle index 598077d73c..9572fc2955 100644 --- a/_ext/gradle/java-setup.gradle +++ b/_ext/gradle/java-setup.gradle @@ -22,4 +22,6 @@ dependencies { testImplementation project(':testlib') } -test { useJUnitPlatform() } +tasks.withType(Test).configureEach { + useJUnitPlatform() +} diff --git a/gradle/special-tests.gradle b/gradle/special-tests.gradle index c435bc7e81..fd5f602243 100644 --- a/gradle/special-tests.gradle +++ b/gradle/special-tests.gradle @@ -7,7 +7,7 @@ def special = [ ] boolean isCiServer = System.getenv().containsKey("CI") -tasks.named('test') { +tasks.withType(Test).configureEach { // See com.diffplug.spotless.tag package for available JUnit 5 @Tag annotations useJUnitPlatform { excludeTags special as String[] diff --git a/lib-extra/build.gradle b/lib-extra/build.gradle index 08bf286d8d..8381d14041 100644 --- a/lib-extra/build.gradle +++ b/lib-extra/build.gradle @@ -26,7 +26,7 @@ dependencies { spotbugs { reportLevel = 'low' } // low|medium|high (low = sensitive to even minor mistakes) apply from: rootProject.file('gradle/special-tests.gradle') -tasks.named('test') { +tasks.withType(Test).configureEach { if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16)) { // needed for EclipseCdtFormatterStepTest jvmArgs '--add-opens=java.base/java.lang=ALL-UNNAMED' diff --git a/plugin-gradle/build.gradle b/plugin-gradle/build.gradle index 11258db0e6..3e44335276 100644 --- a/plugin-gradle/build.gradle +++ b/plugin-gradle/build.gradle @@ -28,7 +28,7 @@ dependencies { } apply from: rootProject.file('gradle/special-tests.gradle') -tasks.named('test') { +tasks.withType(Test).configureEach { testLogging.showStandardStreams = true } diff --git a/testlib/build.gradle b/testlib/build.gradle index 64d4681805..639df8a2ab 100644 --- a/testlib/build.gradle +++ b/testlib/build.gradle @@ -22,7 +22,7 @@ dependencies { spotbugs { reportLevel = 'high' } // low|medium|high (low = sensitive to even minor mistakes) apply from: rootProject.file('gradle/special-tests.gradle') -tasks.named('test') { +tasks.withType(Test).configureEach { if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16)) { // for Antlr4FormatterStepTest and KtLintStepTest def args = [ From c4df046be318e7a780a7e3b9ba100ceb62d1be3a Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 24 Jan 2023 16:43:18 +0800 Subject: [PATCH 44/48] Let check depend on version compatibility tasks https://github.com/davidburstrom/version-compatibility-gradle-plugin#lifecycle-tasks --- lib/build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/build.gradle b/lib/build.gradle index 30c8fe8b15..0b42c7bcc1 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -42,6 +42,11 @@ versionCompatibility { } } +tasks.named("check").configure { + dependsOn(tasks.named("testCompatibilityAdapters")) + dependsOn(tasks.named("testCompatibility")) +} + dependencies { compileOnly 'org.slf4j:slf4j-api:2.0.0' // zero runtime reqs is a hard requirements for spotless-lib From 057f6c42a0dbbe61f7a77ec14828a7f233e6b250 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 24 Jan 2023 17:09:22 +0800 Subject: [PATCH 45/48] Make KtLintCompat0Dot48Dot0AdapterTest workable https://github.com/pinterest/ktlint/blob/2642124cfec306050dd0258870a16dc95aaa106c/build-logic/src/main/kotlin/ToolchainForTests.kt#L16-L35 --- lib/build.gradle | 17 +++++++++++++++++ .../KtLintCompat0Dot48Dot0AdapterTest.java | 7 +++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index 0b42c7bcc1..1a112c78cc 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -119,6 +119,23 @@ dependencies { spotbugs { reportLevel = 'low' } // low|medium|high (low = sensitive to even minor mistakes) apply from: rootProject.file('gradle/special-tests.gradle') +tasks.withType(Test).configureEach { + def jdkVersion = JavaVersion.current().majorVersion.toInteger() + def args = [] + if (jdkVersion >= 16) { + // https://docs.gradle.org/7.5/userguide/upgrading_version_7.html#removes_implicit_add_opens_for_test_workers + args += [ + "--add-opens=java.base/java.lang=ALL-UNNAMED", + "--add-opens=java.base/java.util=ALL-UNNAMED", + ] + } + if (jdkVersion >= 18) { + // https://openjdk.org/jeps/411 + args += "-Djava.security.manager=allow" + } + jvmArgs(args) +} + jar { for (glue in NEEDS_GLUE) { from sourceSets.getByName(glue).output.classesDirs diff --git a/lib/src/testCompatKtLint0Dot48Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot48Dot0AdapterTest.java b/lib/src/testCompatKtLint0Dot48Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot48Dot0AdapterTest.java index 6825b3d818..8804f86b08 100644 --- a/lib/src/testCompatKtLint0Dot48Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot48Dot0AdapterTest.java +++ b/lib/src/testCompatKtLint0Dot48Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot48Dot0AdapterTest.java @@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; @@ -33,12 +34,13 @@ public class KtLintCompat0Dot48Dot0AdapterTest { public void testDefaults(@TempDir Path path) throws IOException { KtLintCompat0Dot48Dot0Adapter ktLintCompat0Dot48Dot0Adapter = new KtLintCompat0Dot48Dot0Adapter(); String text = loadAndWriteText(path, "empty_class_body.kt"); + final Path filePath = Paths.get(path.toString(), "empty_class_body.kt"); Map userData = new HashMap<>(); Map editorConfigOverrideMap = new HashMap<>(); - String formatted = ktLintCompat0Dot48Dot0Adapter.format(text, path, false, false, null, userData, editorConfigOverrideMap); + String formatted = ktLintCompat0Dot48Dot0Adapter.format(text, filePath, false, false, null, userData, editorConfigOverrideMap); assertEquals("class empty_class_body\n", formatted); } @@ -46,6 +48,7 @@ public void testDefaults(@TempDir Path path) throws IOException { public void testEditorConfigCanDisable(@TempDir Path path) throws IOException { KtLintCompat0Dot48Dot0Adapter ktLintCompat0Dot48Dot0Adapter = new KtLintCompat0Dot48Dot0Adapter(); String text = loadAndWriteText(path, "fails_no_semicolons.kt"); + final Path filePath = Paths.get(path.toString(), "fails_no_semicolons.kt"); Map userData = new HashMap<>(); @@ -53,7 +56,7 @@ public void testEditorConfigCanDisable(@TempDir Path path) throws IOException { editorConfigOverrideMap.put("indent_style", "tab"); editorConfigOverrideMap.put("ktlint_standard_no-semi", "disabled"); - String formatted = ktLintCompat0Dot48Dot0Adapter.format(text, path, false, false, null, userData, editorConfigOverrideMap); + String formatted = ktLintCompat0Dot48Dot0Adapter.format(text, filePath, false, false, null, userData, editorConfigOverrideMap); assertEquals("class fails_no_semicolons {\n\tval i = 0;\n}\n", formatted); } From e6b290084e7b1e40a031ae4c1cf954aeef7b5cc9 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 24 Jan 2023 23:13:36 +0800 Subject: [PATCH 46/48] Cleanup usages of gradle/gradle-build-action - Remove cache from actions/setup-java. - Enable gradle-home-cache-cleanup flag, see https://github.com/gradle/gradle-build-action/blob/main/README.md#removing-unused-files-from-gradle-user-home-before-saving-to-cache. --- .github/workflows/changelog-print.yml | 3 ++- .github/workflows/ci.yml | 10 ++++++++-- .github/workflows/deploy.yml | 3 ++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/changelog-print.yml b/.github/workflows/changelog-print.yml index 330f78cda0..a3009e3a39 100644 --- a/.github/workflows/changelog-print.yml +++ b/.github/workflows/changelog-print.yml @@ -15,7 +15,8 @@ jobs: with: java-version: 11 distribution: 'temurin' - cache: 'gradle' - name: gradle caching uses: gradle/gradle-build-action@v2 + with: + gradle-home-cache-cleanup: true - run: ./gradlew changelogPrint diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f08c201f5..86cf955059 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,10 @@ jobs: with: distribution: "temurin" java-version: 11 - cache: gradle + - name: gradle caching + uses: gradle/gradle-build-action@v2 + with: + gradle-home-cache-cleanup: true - name: spotlessCheck run: ./gradlew spotlessCheck --build-cache - name: assemble testClasses @@ -60,7 +63,10 @@ jobs: with: distribution: "temurin" java-version: ${{ matrix.jre }} - cache: gradle + - name: gradle caching + uses: gradle/gradle-build-action@v2 + with: + gradle-home-cache-cleanup: true - name: build (maven-only) if: matrix.kind == 'maven' run: ./gradlew :plugin-maven:build -x spotlessCheck --build-cache diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9a1dcdf609..d41dc8fc30 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -42,9 +42,10 @@ jobs: with: java-version: 11 distribution: 'temurin' - cache: 'gradle' - name: gradle caching uses: gradle/gradle-build-action@v2 + with: + gradle-home-cache-cleanup: true - name: publish all if: "${{ github.event.inputs.to_publish == 'all' }}" run: | From 8be92ffc42d6beb9a62397b9d0d9cccb075e9b90 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 24 Jan 2023 23:17:01 +0800 Subject: [PATCH 47/48] Mark bat files end with CRLF --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index e6141d8894..dc4cb6b0bd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ * text eol=lf +*.bat eol=crlf *.png binary *.jar binary From 92b3cf85596384115ea04d41a039b5e3c47bfb7f Mon Sep 17 00:00:00 2001 From: Abel Keszei Date: Tue, 24 Jan 2023 21:17:32 +0100 Subject: [PATCH 48/48] keep old signature of GsonStep.create() and restore GsonConfig classname --- .../diffplug/spotless/json/gson/GsonStep.java | 5 +++++ .../gradle/spotless/JsonExtension.java | 19 +++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java index 03baec5104..ec90255b77 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java +++ b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java @@ -30,6 +30,11 @@ public class GsonStep { private static final String MAVEN_COORDINATES = "com.google.code.gson:gson"; private static final String INCOMPATIBLE_ERROR_MESSAGE = "There was a problem interacting with Gson; maybe you set an incompatible version?"; + @Deprecated + public static FormatterStep create(int indentSpaces, boolean sortByKeys, boolean escapeHtml, String version, Provisioner provisioner) { + return create(new GsonConfig(sortByKeys, escapeHtml, indentSpaces, version), provisioner); + } + public static FormatterStep create(GsonConfig gsonConfig, Provisioner provisioner) { Objects.requireNonNull(provisioner, "provisioner cannot be null"); return FormatterStep.createLazy("gson", () -> new State(gsonConfig, provisioner), State::toFormatter); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java index c684dab291..39b158ce1e 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java @@ -23,7 +23,6 @@ import com.diffplug.spotless.json.JacksonJsonConfig; import com.diffplug.spotless.json.JacksonJsonStep; import com.diffplug.spotless.json.JsonSimpleStep; -import com.diffplug.spotless.json.gson.GsonConfig; import com.diffplug.spotless.json.gson.GsonStep; public class JsonExtension extends FormatExtension { @@ -48,8 +47,8 @@ public SimpleConfig simple() { return new SimpleConfig(DEFAULT_INDENTATION); } - public GsonGradleConfig gson() { - return new GsonGradleConfig(); + public GsonConfig gson() { + return new GsonConfig(); } public JacksonJsonGradleConfig jackson() { @@ -74,13 +73,13 @@ private FormatterStep createStep() { } } - public class GsonGradleConfig { + public class GsonConfig { private int indentSpaces; private boolean sortByKeys; private boolean escapeHtml; private String version; - public GsonGradleConfig() { + public GsonConfig() { this.indentSpaces = DEFAULT_INDENTATION; this.sortByKeys = false; this.escapeHtml = false; @@ -88,32 +87,32 @@ public GsonGradleConfig() { addStep(createStep()); } - public GsonGradleConfig indentWithSpaces(int indentSpaces) { + public GsonConfig indentWithSpaces(int indentSpaces) { this.indentSpaces = indentSpaces; replaceStep(createStep()); return this; } - public GsonGradleConfig sortByKeys() { + public GsonConfig sortByKeys() { this.sortByKeys = true; replaceStep(createStep()); return this; } - public GsonGradleConfig escapeHtml() { + public GsonConfig escapeHtml() { this.escapeHtml = true; replaceStep(createStep()); return this; } - public GsonGradleConfig version(String version) { + public GsonConfig version(String version) { this.version = version; replaceStep(createStep()); return this; } private FormatterStep createStep() { - return GsonStep.create(new GsonConfig(sortByKeys, escapeHtml, indentSpaces, version), provisioner()); + return GsonStep.create(new com.diffplug.spotless.json.gson.GsonConfig(sortByKeys, escapeHtml, indentSpaces, version), provisioner()); } }