From cf00d4de4161d738112068f0ca5e3eb76b3318f5 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Wed, 3 Jun 2020 20:25:46 +0200 Subject: [PATCH 01/43] poc: call native node-server from within java --- .../spotless/npm/PrettierFormatterStep.java | 278 +++++++++--------- .../spotless/npm/PrettierRestService.java | 128 ++++++++ .../spotless/npm/SimpleJsonWriter.java | 84 +++++- .../spotless/npm/prettier-package.json | 3 +- .../npm/PrettierFormatterStepTest.java | 2 +- 5 files changed, 355 insertions(+), 140 deletions(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.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 a7161065ef..2d79bc6738 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -15,9 +15,13 @@ */ package com.diffplug.spotless.npm; -import static java.util.Arrays.asList; -import static java.util.Objects.requireNonNull; +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.Provisioner; +import com.diffplug.spotless.ThrowingEx; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.io.Serializable; @@ -25,140 +29,144 @@ import java.util.Map; import java.util.TreeMap; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import com.diffplug.spotless.FormatterFunc; -import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.Provisioner; -import com.diffplug.spotless.ThrowingEx; +import static java.util.Objects.requireNonNull; public class PrettierFormatterStep { - public static final String NAME = "prettier-format"; - - public static final Map defaultDevDependencies() { - return defaultDevDependenciesWithPrettier("1.16.4"); - } - - public static final Map defaultDevDependenciesWithPrettier(String version) { - return Collections.singletonMap("prettier", version); - } - - @Deprecated - public static FormatterStep create(Provisioner provisioner, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) { - return create(defaultDevDependencies(), provisioner, buildDir, npm, prettierConfig); - } - - public static FormatterStep create(Map devDependencies, Provisioner provisioner, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) { - requireNonNull(devDependencies); - requireNonNull(provisioner); - requireNonNull(buildDir); - return FormatterStep.createLazy(NAME, - () -> new State(NAME, devDependencies, provisioner, buildDir, npm, prettierConfig), - State::createFormatterFunc); - } - - public static class State extends NpmFormatterStepStateBase implements Serializable { - - private static final long serialVersionUID = -3811104513825329168L; - private final PrettierConfig prettierConfig; - - State(String stepName, Map devDependencies, Provisioner provisioner, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) throws IOException { - super(stepName, - provisioner, - new NpmConfig( - replaceDevDependencies( - readFileFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-package.json"), - new TreeMap<>(devDependencies)), - "prettier"), - buildDir, - npm); - this.prettierConfig = requireNonNull(prettierConfig); - } - - @Override - @Nonnull - public FormatterFunc createFormatterFunc() { - - try { - final NodeJSWrapper nodeJSWrapper = nodeJSWrapper(); - final V8ObjectWrapper prettier = nodeJSWrapper.require(nodeModulePath()); - - @SuppressWarnings("unchecked") - final Map[] resolvedPrettierOptions = (Map[]) new Map[1]; - - if (this.prettierConfig.getPrettierConfigPath() != null) { - final Exception[] toThrow = new Exception[1]; - try ( - V8FunctionWrapper resolveConfigCallback = createResolveConfigFunction(nodeJSWrapper, resolvedPrettierOptions, toThrow); - V8ObjectWrapper resolveConfigOption = createResolveConfigOptionObj(nodeJSWrapper); - V8ArrayWrapper resolveConfigParams = createResolveConfigParamsArray(nodeJSWrapper, resolveConfigOption); - - V8ObjectWrapper promise = prettier.executeObjectFunction("resolveConfig", resolveConfigParams); - V8ArrayWrapper callbacks = nodeJSWrapper.createNewArray(resolveConfigCallback);) { - - promise.executeVoidFunction("then", callbacks); - executeResolution(nodeJSWrapper, resolvedPrettierOptions, toThrow); - } - } else { - resolvedPrettierOptions[0] = this.prettierConfig.getOptions(); - } - - final V8ObjectWrapper prettierConfig = nodeJSWrapper.createNewObject(resolvedPrettierOptions[0]); - - return FormatterFunc.Closeable.of(() -> { - asList(prettierConfig, prettier, nodeJSWrapper).forEach(ReflectiveObjectWrapper::release); - }, input -> { - try (V8ArrayWrapper formatParams = nodeJSWrapper.createNewArray(input, prettierConfig)) { - String result = prettier.executeStringFunction("format", formatParams); - return result; - } - }); - } catch (Exception e) { - throw ThrowingEx.asRuntime(e); - } - } - - private V8FunctionWrapper createResolveConfigFunction(NodeJSWrapper nodeJSWrapper, Map[] outputOptions, Exception[] toThrow) { - return nodeJSWrapper.createNewFunction((receiver, parameters) -> { - try { - try (final V8ObjectWrapper configOptions = parameters.getObject(0)) { - if (configOptions == null) { - toThrow[0] = new IllegalArgumentException("Cannot find or read config file " + this.prettierConfig.getPrettierConfigPath()); - } else { - Map resolvedOptions = new TreeMap<>(V8ObjectUtilsWrapper.toMap(configOptions)); - resolvedOptions.putAll(this.prettierConfig.getOptions()); - outputOptions[0] = resolvedOptions; - } - } - } catch (Exception e) { - toThrow[0] = e; - } - return receiver; - }); - } - - private V8ObjectWrapper createResolveConfigOptionObj(NodeJSWrapper nodeJSWrapper) { - return nodeJSWrapper.createNewObject() - .add("config", this.prettierConfig.getPrettierConfigPath().getAbsolutePath()); - } - - private V8ArrayWrapper createResolveConfigParamsArray(NodeJSWrapper nodeJSWrapper, V8ObjectWrapper resolveConfigOption) { - return nodeJSWrapper.createNewArray() - .pushNull() - .push(resolveConfigOption); - } - - private void executeResolution(NodeJSWrapper nodeJSWrapper, Map[] resolvedPrettierOptions, Exception[] toThrow) { - while (resolvedPrettierOptions[0] == null && toThrow[0] == null) { - nodeJSWrapper.handleMessage(); - } - - if (toThrow[0] != null) { - throw ThrowingEx.asRuntime(toThrow[0]); - } - } - - } + public static final String NAME = "prettier-format"; + + public static final Map defaultDevDependencies() { + return defaultDevDependenciesWithPrettier("1.16.4"); + } + + public static final Map defaultDevDependenciesWithPrettier(String version) { + return Collections.singletonMap("prettier", version); + } + + @Deprecated + public static FormatterStep create(Provisioner provisioner, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) { + return create(defaultDevDependencies(), provisioner, buildDir, npm, prettierConfig); + } + + public static FormatterStep create(Map devDependencies, Provisioner provisioner, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) { + requireNonNull(devDependencies); + requireNonNull(provisioner); + requireNonNull(buildDir); + return FormatterStep.createLazy(NAME, + () -> new State(NAME, devDependencies, provisioner, buildDir, npm, prettierConfig), + State::createFormatterFunc); + } + + public static class State extends NpmFormatterStepStateBase implements Serializable { + + private static final long serialVersionUID = -3811104513825329168L; + private final PrettierConfig prettierConfig; + + State(String stepName, Map devDependencies, Provisioner provisioner, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) throws IOException { + super(stepName, + provisioner, + new NpmConfig( + replaceDevDependencies( + readFileFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-package.json"), + new TreeMap<>(devDependencies)), + "prettier"), + buildDir, + npm); + this.prettierConfig = requireNonNull(prettierConfig); + } + + @Override + @Nonnull + public FormatterFunc createFormatterFunc() { + + try { + PrettierRestService restService = new PrettierRestService(); + + +// final NodeJSWrapper nodeJSWrapper = nodeJSWrapper(); +// final V8ObjectWrapper prettier = nodeJSWrapper.require(nodeModulePath()); +// +// @SuppressWarnings("unchecked") +// final Map[] resolvedPrettierOptions = (Map[]) new Map[1]; + + final String prettierOptionsJson; + final String prettierOverrideOptionsJson; + if (this.prettierConfig.getPrettierConfigPath() != null) { + +// final Exception[] toThrow = new Exception[1]; +// try ( +// V8FunctionWrapper resolveConfigCallback = createResolveConfigFunction(nodeJSWrapper, resolvedPrettierOptions, toThrow); +// V8ObjectWrapper resolveConfigOption = createResolveConfigOptionObj(nodeJSWrapper); +// V8ArrayWrapper resolveConfigParams = createResolveConfigParamsArray(nodeJSWrapper, resolveConfigOption); +// +// V8ObjectWrapper promise = prettier.executeObjectFunction("resolveConfig", resolveConfigParams); +// V8ArrayWrapper callbacks = nodeJSWrapper.createNewArray(resolveConfigCallback);) { +// +// promise.executeVoidFunction("then", callbacks); +// executeResolution(nodeJSWrapper, resolvedPrettierOptions, toThrow); +// } + prettierOptionsJson = restService.resolveConfig(this.prettierConfig.getPrettierConfigPath()); + prettierOverrideOptionsJson = SimpleJsonWriter.of(this.prettierConfig.getOptions()).toJsonString(); + } else { + prettierOptionsJson = null; + prettierOverrideOptionsJson = SimpleJsonWriter.of(this.prettierConfig.getOptions()).toJsonString(); +// resolvedPrettierOptions[0] = this.prettierConfig.getOptions(); + } + +// final V8ObjectWrapper prettierConfig = nodeJSWrapper.createNewObject(resolvedPrettierOptions[0]); + return input -> restService.format(input, prettierOptionsJson, prettierOverrideOptionsJson); +// return FormatterFunc.Closeable.of(() -> { +// asList(prettierConfig, prettier, nodeJSWrapper).forEach(ReflectiveObjectWrapper::release); +// }, input -> { +// try (V8ArrayWrapper formatParams = nodeJSWrapper.createNewArray(input, prettierConfig)) { +// String result = prettier.executeStringFunction("format", formatParams); +// return result; +// } +// }); + } catch (Exception e) { + throw ThrowingEx.asRuntime(e); + } + } + + private V8FunctionWrapper createResolveConfigFunction(NodeJSWrapper nodeJSWrapper, Map[] outputOptions, Exception[] toThrow) { + return nodeJSWrapper.createNewFunction((receiver, parameters) -> { + try { + try (final V8ObjectWrapper configOptions = parameters.getObject(0)) { + if (configOptions == null) { + toThrow[0] = new IllegalArgumentException("Cannot find or read config file " + this.prettierConfig.getPrettierConfigPath()); + } else { + Map resolvedOptions = new TreeMap<>(V8ObjectUtilsWrapper.toMap(configOptions)); + resolvedOptions.putAll(this.prettierConfig.getOptions()); + outputOptions[0] = resolvedOptions; + } + } + } catch (Exception e) { + toThrow[0] = e; + } + return receiver; + }); + } + + private V8ObjectWrapper createResolveConfigOptionObj(NodeJSWrapper nodeJSWrapper) { + return nodeJSWrapper.createNewObject() + .add("config", this.prettierConfig.getPrettierConfigPath().getAbsolutePath()); + } + + private V8ArrayWrapper createResolveConfigParamsArray(NodeJSWrapper nodeJSWrapper, V8ObjectWrapper resolveConfigOption) { + return nodeJSWrapper.createNewArray() + .pushNull() + .push(resolveConfigOption); + } + + private void executeResolution(NodeJSWrapper nodeJSWrapper, Map[] resolvedPrettierOptions, Exception[] toThrow) { + while (resolvedPrettierOptions[0] == null && toThrow[0] == null) { + nodeJSWrapper.handleMessage(); + } + + if (toThrow[0] != null) { + throw ThrowingEx.asRuntime(toThrow[0]); + } + } + + } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java new file mode 100644 index 0000000000..43d46e6857 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020 Ergon Informatik AG + * Merkurstrasse 43, 8032 Zuerich, Switzerland + * All rights reserved. + */ + +package com.diffplug.spotless.npm; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class PrettierRestService { + + private static final Pattern ESCAPED_LINE_TERMINATOR_PATTERN = Pattern.compile("\\r\\n"); + + PrettierRestService() { + } + + + // /prettier/config-options + public String resolveConfig(File prettierConfigPath) { + try { + URL url = new URL("http://localhost:3000/prettier/config-options"); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/json"); + + Map jsonProperties = new LinkedHashMap<>(); + jsonProperties.put("config_file_path", prettierConfigPath.getAbsolutePath()); + + final SimpleJsonWriter jsonWriter = SimpleJsonWriter.of(jsonProperties); + final String jsonString = jsonWriter.toJsonString(); + + con.setDoOutput(true); + DataOutputStream out = new DataOutputStream(con.getOutputStream()); + out.writeBytes(jsonString); + out.flush(); + out.close(); + + int status = con.getResponseCode(); + + if (status != 200) { + throw new RuntimeException("Received status " + status + " instead of 200. " + con.getResponseMessage()); + } + + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + String inputLine; + StringBuffer content = new StringBuffer(); + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + in.close(); + return content.toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String format(String fileContent, String optionsJsonString, String optionsOverrideJsonString) { + try { + URL url = new URL("http://localhost:3000/prettier/format"); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/json"); + + + Map jsonProperties = new LinkedHashMap<>(); + jsonProperties.put("file_content", fileContent); + if (optionsJsonString != null) { + jsonProperties.put("resolved_config_options", SimpleJsonWriter.RawJsonValue.asRawJson(optionsJsonString)); + } + if (optionsOverrideJsonString != null) { + jsonProperties.put("config_options", SimpleJsonWriter.RawJsonValue.asRawJson(optionsOverrideJsonString)); + } + final SimpleJsonWriter jsonWriter = SimpleJsonWriter.of(jsonProperties); + final String jsonString = jsonWriter.toJsonString(); + con.setDoOutput(true); + DataOutputStream out = new DataOutputStream(con.getOutputStream()); + out.writeBytes(jsonString); + out.flush(); + out.close(); + + int status = con.getResponseCode(); + + if (status != 200) { + throw new RuntimeException("Received status " + status + " instead of 200 " + con.getResponseMessage()); + } + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + InputStream input = con.getInputStream(); + + byte[] buffer = new byte[1024]; + int numRead; + while ((numRead = input.read(buffer)) != -1) { + output.write(buffer, 0, numRead); + } + return output.toString(StandardCharsets.UTF_8.name()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static class ParameterStringBuilder { + public static String getParamsString(Map params) + throws UnsupportedEncodingException { + StringBuilder result = new StringBuilder(); + + for (Map.Entry entry : params.entrySet()) { + result.append(URLEncoder.encode(entry.getKey(), "UTF-8")); + result.append("="); + result.append(URLEncoder.encode(entry.getValue(), "UTF-8")); + result.append("&"); + } + + String resultString = result.toString(); + return resultString.length() > 0 + ? resultString.substring(0, resultString.length() - 1) + : resultString; + } + } + +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java index 8650f086c4..4346ba88c9 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java @@ -53,8 +53,8 @@ SimpleJsonWriter put(String name, Object value) { private void verifyValues(Map values) { if (values.values() .stream() - .anyMatch(val -> !(val instanceof String || val instanceof Number || val instanceof Boolean))) { - throw new IllegalArgumentException("Only values of type 'String', 'Number' and 'Boolean' are supported. You provided: " + values.values()); + .anyMatch(val -> !(val instanceof String || val instanceof RawJsonValue|| val instanceof Number || val instanceof Boolean))) { + throw new IllegalArgumentException("Only values of type 'String', 'RawJsonValue', 'Number' and 'Boolean' are supported. You provided: " + values.values()); } } @@ -68,8 +68,70 @@ String toJsonString() { private String jsonEscape(Object val) { requireNonNull(val); + if (val instanceof RawJsonValue) { + return ((RawJsonValue) val).getRawJson(); + } if (val instanceof String) { - return "\"" + val + "\""; + /** + * the following characters are reserved in JSON and must be properly escaped to be used in strings: + * + * Backspace is replaced with \b + * Form feed is replaced with \f + * Newline is replaced with \n + * Carriage return is replaced with \r + * Tab is replaced with \t + * Double quote is replaced with \" + * Backslash is replaced with \\ + */ + StringBuilder escaped = new StringBuilder(); + escaped.append('"'); + char b; + char c = 0; + for(int i = 0; i< ((String)val).length();i++) { + b = c; + c = ((String) val).charAt(i); + switch (c) { + case '\"': + escaped.append('\\').append('"'); + break; + case '\n': + escaped.append('\\').append('n'); + break; + case '\r': + escaped.append('\\').append('r'); + break; + case '\t': + escaped.append('\\').append('t'); + break; + case '\b': + escaped.append('\\').append('b'); + break; + case '\f': + escaped.append('\\').append('f'); + break; + case '\\': + escaped.append('\\').append('\\'); + break; + case '/': + if (b == '<') { + escaped.append('\\'); + } + escaped.append(c); + break; + default: + if (c < ' ' || (c >= '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100')) { + escaped.append('\\').append('u'); + String hexString = Integer.toHexString(c); + escaped.append("0000", 0, 4 - hexString.length()); + escaped.append(hexString); + } else { + escaped.append(c); + } + } + } + escaped.append('"'); + return escaped.toString(); } return val.toString(); } @@ -91,4 +153,20 @@ void toJsonFile(File file) { public String toString() { return this.toJsonString(); } + + static class RawJsonValue { + private final String rawJson; + + private RawJsonValue(String rawJson) { + this.rawJson = requireNonNull(rawJson); + } + + static RawJsonValue asRawJson(String rawJson) { + return new RawJsonValue(rawJson); + } + + public String getRawJson() { + return rawJson; + } + } } diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json index c46cf46a3d..5921bad6a0 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json @@ -2,7 +2,8 @@ "name": "spotless-prettier-formatter-step", "version": "1.0.0", "devDependencies": { -${devDependencies} +${devDependencies}, + "express": "4.17.1" }, "dependencies": {}, "engines": { diff --git a/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java b/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java index 796f6dcc43..613bd92955 100644 --- a/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java @@ -41,7 +41,7 @@ public static class PrettierFormattingOfFileTypesIsWorking extends NpmFormatterS @Parameterized.Parameters(name = "{index}: prettier can be applied to {0}") public static Iterable formattingConfigFiles() { - return Arrays.asList("typescript", "json", "javascript-es5", "javascript-es6", "css", "scss", "markdown", "yaml"); + return Arrays.asList("html", "typescript", "json", "javascript-es5", "javascript-es6", "css", "scss", "markdown", "yaml"); } @Test From b07af272b14234577d8d11358de704de8f08b177 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Fri, 5 Jun 2020 17:53:57 +0200 Subject: [PATCH 02/43] resolve prettier config once only --- .../spotless/npm/PrettierFormatterStep.java | 16 +++++------- .../spotless/npm/PrettierRestService.java | 26 ++++++++++--------- 2 files changed, 20 insertions(+), 22 deletions(-) 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 2d79bc6738..bad5284276 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -89,9 +89,9 @@ public FormatterFunc createFormatterFunc() { // @SuppressWarnings("unchecked") // final Map[] resolvedPrettierOptions = (Map[]) new Map[1]; - final String prettierOptionsJson; - final String prettierOverrideOptionsJson; - if (this.prettierConfig.getPrettierConfigPath() != null) { +// final String prettierOptionsJson; +// final String prettierOverrideOptionsJson; +// if (this.prettierConfig.getPrettierConfigPath() != null) { // final Exception[] toThrow = new Exception[1]; // try ( @@ -105,16 +105,12 @@ public FormatterFunc createFormatterFunc() { // promise.executeVoidFunction("then", callbacks); // executeResolution(nodeJSWrapper, resolvedPrettierOptions, toThrow); // } - prettierOptionsJson = restService.resolveConfig(this.prettierConfig.getPrettierConfigPath()); - prettierOverrideOptionsJson = SimpleJsonWriter.of(this.prettierConfig.getOptions()).toJsonString(); - } else { - prettierOptionsJson = null; - prettierOverrideOptionsJson = SimpleJsonWriter.of(this.prettierConfig.getOptions()).toJsonString(); + String prettierConfigOptions = restService.resolveConfig(this.prettierConfig.getPrettierConfigPath(), this.prettierConfig.getOptions()); // resolvedPrettierOptions[0] = this.prettierConfig.getOptions(); - } +// } // final V8ObjectWrapper prettierConfig = nodeJSWrapper.createNewObject(resolvedPrettierOptions[0]); - return input -> restService.format(input, prettierOptionsJson, prettierOverrideOptionsJson); + return input -> restService.format(input, prettierConfigOptions); // return FormatterFunc.Closeable.of(() -> { // asList(prettierConfig, prettier, nodeJSWrapper).forEach(ReflectiveObjectWrapper::release); // }, input -> { diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java index 43d46e6857..1b5f58b282 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java @@ -17,22 +17,26 @@ public class PrettierRestService { - private static final Pattern ESCAPED_LINE_TERMINATOR_PATTERN = Pattern.compile("\\r\\n"); - PrettierRestService() { } // /prettier/config-options - public String resolveConfig(File prettierConfigPath) { + public String resolveConfig(File prettierConfigPath, Map prettierConfigOptions) { try { - URL url = new URL("http://localhost:3000/prettier/config-options"); + URL url = new URL("http://localhost:52429/prettier/config-options"); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("POST"); con.setRequestProperty("Content-Type", "application/json"); Map jsonProperties = new LinkedHashMap<>(); - jsonProperties.put("config_file_path", prettierConfigPath.getAbsolutePath()); + if (prettierConfigPath != null) { + jsonProperties.put("prettier_config_path", prettierConfigPath.getAbsolutePath()); + } + if (prettierConfigOptions != null) { + jsonProperties.put("prettier_config_options", SimpleJsonWriter.RawJsonValue.asRawJson(SimpleJsonWriter.of(prettierConfigOptions).toJsonString())); + + } final SimpleJsonWriter jsonWriter = SimpleJsonWriter.of(jsonProperties); final String jsonString = jsonWriter.toJsonString(); @@ -62,9 +66,9 @@ public String resolveConfig(File prettierConfigPath) { } } - public String format(String fileContent, String optionsJsonString, String optionsOverrideJsonString) { + public String format(String fileContent, String configOptionsJsonString) { try { - URL url = new URL("http://localhost:3000/prettier/format"); + URL url = new URL("http://localhost:52429/prettier/format"); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("POST"); con.setRequestProperty("Content-Type", "application/json"); @@ -72,12 +76,10 @@ public String format(String fileContent, String optionsJsonString, String option Map jsonProperties = new LinkedHashMap<>(); jsonProperties.put("file_content", fileContent); - if (optionsJsonString != null) { - jsonProperties.put("resolved_config_options", SimpleJsonWriter.RawJsonValue.asRawJson(optionsJsonString)); - } - if (optionsOverrideJsonString != null) { - jsonProperties.put("config_options", SimpleJsonWriter.RawJsonValue.asRawJson(optionsOverrideJsonString)); + if (configOptionsJsonString != null) { + jsonProperties.put("config_options", SimpleJsonWriter.RawJsonValue.asRawJson(configOptionsJsonString)); } + final SimpleJsonWriter jsonWriter = SimpleJsonWriter.of(jsonProperties); final String jsonString = jsonWriter.toJsonString(); con.setDoOutput(true); From 2180d358f3cb88e7144d0d2797de68412b4e98bc Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Fri, 5 Jun 2020 19:51:04 +0200 Subject: [PATCH 03/43] extract http/url handling part into a pretty simple client... --- .../spotless/npm/PrettierFormatterStep.java | 2 +- .../spotless/npm/PrettierRestService.java | 121 +++--------------- .../spotless/npm/SimpleRestClient.java | 118 +++++++++++++++++ 3 files changed, 136 insertions(+), 105 deletions(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.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 bad5284276..8e1ac09ee3 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -80,7 +80,7 @@ public static class State extends NpmFormatterStepStateBase implements Serializa public FormatterFunc createFormatterFunc() { try { - PrettierRestService restService = new PrettierRestService(); + PrettierRestService restService = new PrettierRestService("http://localhost:52429"); // final NodeJSWrapper nodeJSWrapper = nodeJSWrapper(); diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java index 1b5f58b282..f74f5519df 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java @@ -6,125 +6,38 @@ package com.diffplug.spotless.npm; -import java.io.*; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; +import java.io.File; import java.util.LinkedHashMap; import java.util.Map; -import java.util.regex.Pattern; public class PrettierRestService { - PrettierRestService() { - } + private final SimpleRestClient restClient; + PrettierRestService(String baseUrl) { + this.restClient = SimpleRestClient.forBaseUrl(baseUrl); + } - // /prettier/config-options public String resolveConfig(File prettierConfigPath, Map prettierConfigOptions) { - try { - URL url = new URL("http://localhost:52429/prettier/config-options"); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setRequestMethod("POST"); - con.setRequestProperty("Content-Type", "application/json"); - - Map jsonProperties = new LinkedHashMap<>(); - if (prettierConfigPath != null) { - jsonProperties.put("prettier_config_path", prettierConfigPath.getAbsolutePath()); - } - if (prettierConfigOptions != null) { - jsonProperties.put("prettier_config_options", SimpleJsonWriter.RawJsonValue.asRawJson(SimpleJsonWriter.of(prettierConfigOptions).toJsonString())); - - } - - final SimpleJsonWriter jsonWriter = SimpleJsonWriter.of(jsonProperties); - final String jsonString = jsonWriter.toJsonString(); - - con.setDoOutput(true); - DataOutputStream out = new DataOutputStream(con.getOutputStream()); - out.writeBytes(jsonString); - out.flush(); - out.close(); - - int status = con.getResponseCode(); - - if (status != 200) { - throw new RuntimeException("Received status " + status + " instead of 200. " + con.getResponseMessage()); - } + Map jsonProperties = new LinkedHashMap<>(); + if (prettierConfigPath != null) { + jsonProperties.put("prettier_config_path", prettierConfigPath.getAbsolutePath()); + } + if (prettierConfigOptions != null) { + jsonProperties.put("prettier_config_options", SimpleJsonWriter.RawJsonValue.asRawJson(SimpleJsonWriter.of(prettierConfigOptions).toJsonString())); - BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); - String inputLine; - StringBuffer content = new StringBuffer(); - while ((inputLine = in.readLine()) != null) { - content.append(inputLine); - } - in.close(); - return content.toString(); - } catch (IOException e) { - throw new RuntimeException(e); } + return restClient.postJson("/prettier/config-options", jsonProperties); } public String format(String fileContent, String configOptionsJsonString) { - try { - URL url = new URL("http://localhost:52429/prettier/format"); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setRequestMethod("POST"); - con.setRequestProperty("Content-Type", "application/json"); - - - Map jsonProperties = new LinkedHashMap<>(); - jsonProperties.put("file_content", fileContent); - if (configOptionsJsonString != null) { - jsonProperties.put("config_options", SimpleJsonWriter.RawJsonValue.asRawJson(configOptionsJsonString)); - } - - final SimpleJsonWriter jsonWriter = SimpleJsonWriter.of(jsonProperties); - final String jsonString = jsonWriter.toJsonString(); - con.setDoOutput(true); - DataOutputStream out = new DataOutputStream(con.getOutputStream()); - out.writeBytes(jsonString); - out.flush(); - out.close(); - - int status = con.getResponseCode(); - - if (status != 200) { - throw new RuntimeException("Received status " + status + " instead of 200 " + con.getResponseMessage()); - } - - ByteArrayOutputStream output = new ByteArrayOutputStream(); - InputStream input = con.getInputStream(); - - byte[] buffer = new byte[1024]; - int numRead; - while ((numRead = input.read(buffer)) != -1) { - output.write(buffer, 0, numRead); - } - return output.toString(StandardCharsets.UTF_8.name()); - } catch (IOException e) { - throw new RuntimeException(e); + Map jsonProperties = new LinkedHashMap<>(); + jsonProperties.put("file_content", fileContent); + if (configOptionsJsonString != null) { + jsonProperties.put("config_options", SimpleJsonWriter.RawJsonValue.asRawJson(configOptionsJsonString)); } - } - - public static class ParameterStringBuilder { - public static String getParamsString(Map params) - throws UnsupportedEncodingException { - StringBuilder result = new StringBuilder(); - for (Map.Entry entry : params.entrySet()) { - result.append(URLEncoder.encode(entry.getKey(), "UTF-8")); - result.append("="); - result.append(URLEncoder.encode(entry.getValue(), "UTF-8")); - result.append("&"); - } - - String resultString = result.toString(); - return resultString.length() > 0 - ? resultString.substring(0, resultString.length() - 1) - : resultString; - } + return restClient.postJson("/prettier/format", jsonProperties); } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java new file mode 100644 index 0000000000..41b02df6c3 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020 Ergon Informatik AG + * Merkurstrasse 43, 8032 Zuerich, Switzerland + * All rights reserved. + */ + +package com.diffplug.spotless.npm; + +import javax.annotation.Nonnull; +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Map; + +import static java.util.Objects.requireNonNull; + +class SimpleRestClient { + private final String baseUrl; + + private SimpleRestClient(String baseUrl) { + this.baseUrl = requireNonNull(baseUrl); + } + + static SimpleRestClient forBaseUrl(String baseUrl) { + return new SimpleRestClient(baseUrl); + } + + String postJson(String endpoint, Map jsonParams) throws SimpleRestException { + final SimpleJsonWriter jsonWriter = SimpleJsonWriter.of(jsonParams); + final String jsonString = jsonWriter.toJsonString(); + + return postJson(endpoint, jsonString); + } + + String postJson(String endpoint, String rawJson) throws SimpleRestException { + try { + URL url = new URL(this.baseUrl + endpoint); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/json"); + con.setDoOutput(true); + DataOutputStream out = new DataOutputStream(con.getOutputStream()); + out.writeBytes(rawJson); + out.flush(); + out.close(); + + int status = con.getResponseCode(); + + if (status != 200) { + throw new SimpleRestResponseException(status, con.getResponseMessage(), "Unexpected response status code."); + } + + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + String inputLine; + StringBuilder content = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + in.close(); + return content.toString(); + } catch (IOException e) { + throw new SimpleRestIOException(e); + } + } + + + static abstract class SimpleRestException extends RuntimeException { + public SimpleRestException() { + } + + public SimpleRestException(Throwable cause) { + super(cause); + } + } + + static class SimpleRestResponseException extends SimpleRestException { + private final int statusCode; + + private final String responseMessage; + + private final String exceptionMessage; + + public SimpleRestResponseException(int statusCode, String responseMessage, String exceptionmessage) { + this.statusCode = statusCode; + this.responseMessage = responseMessage; + this.exceptionMessage = exceptionmessage; + } + + @Nonnull + public int getStatusCode() { + return statusCode; + } + + @Nonnull + public String getResponseMessage() { + return responseMessage; + } + + @Nonnull + public String getExceptionMessage() { + return exceptionMessage; + } + + @Override + public String getMessage() { + return String.format("%s: %s (%s)", getStatusCode(), getResponseMessage(), getExceptionMessage()); + } + } + + static class SimpleRestIOException extends SimpleRestException { + public SimpleRestIOException(Throwable cause) { + super(cause); + } + } +} From 3c8d2ca2633f2c2c5d9819b966a2e36e57e6b377 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sat, 6 Jun 2020 21:40:39 +0200 Subject: [PATCH 04/43] raw state: start node server and execute rest calls to it --- .../com/diffplug/spotless/npm/NpmConfig.java | 13 +- .../npm/NpmFormatterStepStateBase.java | 317 +++++++++++------- .../spotless/npm/PrettierFormatterStep.java | 88 +---- .../spotless/npm/SimpleRestClient.java | 30 +- .../spotless/npm/prettier-package.json | 5 +- .../diffplug/spotless/npm/prettier-serve.js | 85 +++++ 6 files changed, 323 insertions(+), 215 deletions(-) create mode 100644 lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java index 55d3683606..b752d10f5d 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java @@ -15,19 +15,23 @@ */ package com.diffplug.spotless.npm; +import javax.annotation.Nonnull; import java.io.Serializable; class NpmConfig implements Serializable { - private static final long serialVersionUID = -1866722789779160491L; + private static final long serialVersionUID = -7660089232952131272L; private final String packageJsonContent; private final String npmModule; - public NpmConfig(String packageJsonContent, String npmModule) { + private final String serveScriptContent; + + public NpmConfig(String packageJsonContent, String npmModule, String serveScriptContent) { this.packageJsonContent = packageJsonContent; this.npmModule = npmModule; + this.serveScriptContent = serveScriptContent; } public String getPackageJsonContent() { @@ -37,4 +41,9 @@ public String getPackageJsonContent() { public String getNpmModule() { return npmModule; } + + @Nonnull + public String getServeScriptContent() { + return serveScriptContent; + } } 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 8b041e3dcb..6f6f00d8fd 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -15,13 +15,11 @@ */ package com.diffplug.spotless.npm; -import static java.util.Objects.requireNonNull; +import com.diffplug.spotless.*; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; +import javax.annotation.Nullable; +import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Collections; @@ -29,122 +27,201 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.concurrent.TimeoutException; -import javax.annotation.Nullable; - -import com.diffplug.spotless.*; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import static java.util.Objects.requireNonNull; abstract class NpmFormatterStepStateBase implements Serializable { - private static final long serialVersionUID = -5849375492831208496L; - - private final JarState jarState; - - @SuppressWarnings("unused") - private final FileSignature nodeModulesSignature; - - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - public final transient File nodeModulesDir; - - private final NpmConfig npmConfig; - - private final String stepName; - - protected NpmFormatterStepStateBase(String stepName, Provisioner provisioner, NpmConfig npmConfig, File buildDir, @Nullable File npm) throws IOException { - this.stepName = requireNonNull(stepName); - this.npmConfig = requireNonNull(npmConfig); - this.jarState = JarState.from(j2v8MavenCoordinate(), requireNonNull(provisioner)); - - this.nodeModulesDir = prepareNodeModules(buildDir, npm); - this.nodeModulesSignature = FileSignature.signAsList(this.nodeModulesDir); - } - - private File prepareNodeModules(File buildDir, @Nullable File npm) throws IOException { - File targetDir = new File(buildDir, "spotless-node-modules-" + stepName); - if (!targetDir.exists()) { - if (!targetDir.mkdirs()) { - throw new IOException("cannot create temp dir for node modules at " + targetDir); - } - } - File packageJsonFile = new File(targetDir, "package.json"); - Files.write(packageJsonFile.toPath(), this.npmConfig.getPackageJsonContent().getBytes(StandardCharsets.UTF_8)); - runNpmInstall(npm, targetDir); - return targetDir; - } - - private void runNpmInstall(@Nullable File npm, File npmProjectDir) throws IOException { - Process npmInstall = new ProcessBuilder() - .inheritIO() - .directory(npmProjectDir) - .command(resolveNpm(npm).getAbsolutePath(), "install") - .start(); - try { - if (npmInstall.waitFor() != 0) { - throw new IOException("Creating npm modules failed with exit code: " + npmInstall.exitValue()); - } - } catch (InterruptedException e) { - throw new IOException("Running npm install was interrupted.", e); - } - } - - private File resolveNpm(@Nullable File npm) { - return Optional.ofNullable(npm) - .orElseGet(() -> NpmExecutableResolver.tryFind() - .orElseThrow(() -> new IllegalStateException("cannot automatically determine npm executable and none was specifically supplied!"))); - } - - protected NodeJSWrapper nodeJSWrapper() { - return new NodeJSWrapper(this.jarState.getClassLoader()); - } - - protected File nodeModulePath() { - return new File(new File(this.nodeModulesDir, "node_modules"), this.npmConfig.getNpmModule()); - } - - static String j2v8MavenCoordinate() { - return "com.eclipsesource.j2v8:j2v8_" + PlatformInfo.normalizedOSName() + "_" + PlatformInfo.normalizedArchName() + ":4.6.0"; - } - - protected static String readFileFromClasspath(Class clazz, String name) { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - try (InputStream input = clazz.getResourceAsStream(name)) { - byte[] buffer = new byte[1024]; - int numRead; - while ((numRead = input.read(buffer)) != -1) { - output.write(buffer, 0, numRead); - } - return output.toString(StandardCharsets.UTF_8.name()); - } catch (IOException e) { - throw ThrowingEx.asRuntime(e); - } - } - - protected static String replaceDevDependencies(String template, Map devDependencies) { - StringBuilder builder = new StringBuilder(); - Iterator> entryIter = devDependencies.entrySet().iterator(); - while (entryIter.hasNext()) { - Map.Entry entry = entryIter.next(); - builder.append("\t\t\""); - builder.append(entry.getKey()); - builder.append("\": \""); - builder.append(entry.getValue()); - builder.append("\""); - if (entryIter.hasNext()) { - builder.append(",\n"); - } - } - return replacePlaceholders(template, Collections.singletonMap("devDependencies", builder.toString())); - } - - private static String replacePlaceholders(String template, Map replacements) { - String result = template; - for (Entry entry : replacements.entrySet()) { - result = result.replaceAll("\\Q${" + entry.getKey() + "}\\E", entry.getValue()); - } - return result; - } - - public abstract FormatterFunc createFormatterFunc(); + private static final long serialVersionUID = -5849375492831208496L; + + private final JarState jarState; + + @SuppressWarnings("unused") + private final FileSignature nodeModulesSignature; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + public final transient File nodeModulesDir; + + private final transient File npmExecutable; + + private final NpmConfig npmConfig; + + private final String stepName; + + protected NpmFormatterStepStateBase(String stepName, Provisioner provisioner, NpmConfig npmConfig, File buildDir, @Nullable File npm) throws IOException { + this.stepName = requireNonNull(stepName); + this.npmConfig = requireNonNull(npmConfig); + this.jarState = JarState.from(j2v8MavenCoordinate(), requireNonNull(provisioner)); + this.npmExecutable = resolveNpm(npm); + + this.nodeModulesDir = prepareNodeServer(buildDir); + this.nodeModulesSignature = FileSignature.signAsList(this.nodeModulesDir); + } + + private File prepareNodeServer(File buildDir) throws IOException { + File targetDir = new File(buildDir, "spotless-node-modules-" + stepName); + if (!targetDir.exists()) { + if (!targetDir.mkdirs()) { + throw new IOException("cannot create temp dir for node modules at " + targetDir); + } + } + writeContentToFile(targetDir, "package.json", this.npmConfig.getPackageJsonContent()); + writeContentToFile(targetDir, "serve.js", this.npmConfig.getServeScriptContent()); + runNpmInstall(targetDir); + return targetDir; + } + + private void writeContentToFile(File targetDir, String s, String packageJsonContent) throws IOException { + File packageJsonFile = new File(targetDir, s); + Files.write(packageJsonFile.toPath(), packageJsonContent.getBytes(StandardCharsets.UTF_8)); + } + + private void runNpmInstall(File npmProjectDir) throws IOException { + Process npmInstall = new ProcessBuilder() + .inheritIO() + .directory(npmProjectDir) + .command(this.npmExecutable.getAbsolutePath(), "install", "--no-audit", "--no-package-lock") + .start(); + try { + if (npmInstall.waitFor() != 0) { + throw new IOException("Creating npm modules failed with exit code: " + npmInstall.exitValue()); + } + } catch (InterruptedException e) { + throw new IOException("Running npm install was interrupted.", e); + } + } + + protected ServerProcessInfo npmRunServer() throws ServerStartException { + try { + Process server = new ProcessBuilder() + .inheritIO() +// .redirectError(new File("/Users/simschla/tmp/npmerror.log")) +// .redirectOutput(new File("/Users/simschla/tmp/npmout.log")) + .directory(this.nodeModulesDir) + .command(this.npmExecutable.getAbsolutePath(), "start") + .start(); + + File serverPortFile = new File(this.nodeModulesDir, "server.port"); + final long startedAt = System.currentTimeMillis(); + while (!serverPortFile.exists() || !serverPortFile.canRead()) { + // wait for at most 10 seconds + if ((System.currentTimeMillis() - startedAt) > (10 * 1000L)) { + // forcibly end the server process + try { + server.destroyForcibly(); + } catch (Throwable t) { + // log this? + } + throw new TimeoutException("The server did not startup in the requested time frame of 10 seconds."); + } + } + // readPort from file + + // read the server.port file for resulting port + String serverPort = readFile(serverPortFile).trim(); + return new ServerProcessInfo(server, serverPort, serverPortFile); + } catch (IOException | TimeoutException e) { + throw new ServerStartException(e); + } + } + + private static File resolveNpm(@Nullable File npm) { + return Optional.ofNullable(npm) + .orElseGet(() -> NpmExecutableResolver.tryFind() + .orElseThrow(() -> new IllegalStateException("cannot automatically determine npm executable and none was specifically supplied!"))); + } + + protected NodeJSWrapper nodeJSWrapper() { + return new NodeJSWrapper(this.jarState.getClassLoader()); + } + + protected File nodeModulePath() { + return new File(new File(this.nodeModulesDir, "node_modules"), this.npmConfig.getNpmModule()); + } + + static String j2v8MavenCoordinate() { + return "com.eclipsesource.j2v8:j2v8_" + PlatformInfo.normalizedOSName() + "_" + PlatformInfo.normalizedArchName() + ":4.6.0"; + } + + protected static String readFileFromClasspath(Class clazz, String name) { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try (InputStream input = clazz.getResourceAsStream(name)) { + byte[] buffer = new byte[1024]; + int numRead; + while ((numRead = input.read(buffer)) != -1) { + output.write(buffer, 0, numRead); + } + return output.toString(StandardCharsets.UTF_8.name()); + } catch (IOException e) { + throw ThrowingEx.asRuntime(e); + } + } + + protected static String readFile(File file) { + try { + return String.join("\n", Files.readAllLines(file.toPath())); + } catch (IOException e) { + throw ThrowingEx.asRuntime(e); + } + } + + protected static String replaceDevDependencies(String template, Map devDependencies) { + StringBuilder builder = new StringBuilder(); + Iterator> entryIter = devDependencies.entrySet().iterator(); + while (entryIter.hasNext()) { + Map.Entry entry = entryIter.next(); + builder.append("\t\t\""); + builder.append(entry.getKey()); + builder.append("\": \""); + builder.append(entry.getValue()); + builder.append("\""); + if (entryIter.hasNext()) { + builder.append(",\n"); + } + } + return replacePlaceholders(template, Collections.singletonMap("devDependencies", builder.toString())); + } + + private static String replacePlaceholders(String template, Map replacements) { + String result = template; + for (Entry entry : replacements.entrySet()) { + result = result.replaceAll("\\Q${" + entry.getKey() + "}\\E", entry.getValue()); + } + return result; + } + + public abstract FormatterFunc createFormatterFunc(); + + protected class ServerProcessInfo implements AutoCloseable { + private final Process server; + private final String serverPort; + private final File serverPortFile; + + public ServerProcessInfo(Process server, String serverPort, File serverPortFile) { + this.server = server; + this.serverPort = serverPort; + this.serverPortFile = serverPortFile; + } + + public String getBaseUrl() { + return "http://127.0.0.1:" + this.serverPort; + } + + @Override + public void close() throws Exception { + if (serverPortFile.exists()) { + serverPortFile.delete(); + } + if (this.server.isAlive()) { + this.server.destroy(); + } + } + } + + protected class ServerStartException extends RuntimeException { + public ServerStartException(Throwable cause) { + super(cause); + } + } } 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 8e1ac09ee3..4249ec7f56 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -69,7 +69,8 @@ public static class State extends NpmFormatterStepStateBase implements Serializa replaceDevDependencies( readFileFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-package.json"), new TreeMap<>(devDependencies)), - "prettier"), + "prettier", + readFileFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-serve.js")), buildDir, npm); this.prettierConfig = requireNonNull(prettierConfig); @@ -80,87 +81,14 @@ public static class State extends NpmFormatterStepStateBase implements Serializa public FormatterFunc createFormatterFunc() { try { - PrettierRestService restService = new PrettierRestService("http://localhost:52429"); - - -// final NodeJSWrapper nodeJSWrapper = nodeJSWrapper(); -// final V8ObjectWrapper prettier = nodeJSWrapper.require(nodeModulePath()); -// -// @SuppressWarnings("unchecked") -// final Map[] resolvedPrettierOptions = (Map[]) new Map[1]; - -// final String prettierOptionsJson; -// final String prettierOverrideOptionsJson; -// if (this.prettierConfig.getPrettierConfigPath() != null) { - -// final Exception[] toThrow = new Exception[1]; -// try ( -// V8FunctionWrapper resolveConfigCallback = createResolveConfigFunction(nodeJSWrapper, resolvedPrettierOptions, toThrow); -// V8ObjectWrapper resolveConfigOption = createResolveConfigOptionObj(nodeJSWrapper); -// V8ArrayWrapper resolveConfigParams = createResolveConfigParamsArray(nodeJSWrapper, resolveConfigOption); -// -// V8ObjectWrapper promise = prettier.executeObjectFunction("resolveConfig", resolveConfigParams); -// V8ArrayWrapper callbacks = nodeJSWrapper.createNewArray(resolveConfigCallback);) { -// -// promise.executeVoidFunction("then", callbacks); -// executeResolution(nodeJSWrapper, resolvedPrettierOptions, toThrow); -// } - String prettierConfigOptions = restService.resolveConfig(this.prettierConfig.getPrettierConfigPath(), this.prettierConfig.getOptions()); -// resolvedPrettierOptions[0] = this.prettierConfig.getOptions(); -// } - -// final V8ObjectWrapper prettierConfig = nodeJSWrapper.createNewObject(resolvedPrettierOptions[0]); - return input -> restService.format(input, prettierConfigOptions); -// return FormatterFunc.Closeable.of(() -> { -// asList(prettierConfig, prettier, nodeJSWrapper).forEach(ReflectiveObjectWrapper::release); -// }, input -> { -// try (V8ArrayWrapper formatParams = nodeJSWrapper.createNewArray(input, prettierConfig)) { -// String result = prettier.executeStringFunction("format", formatParams); -// return result; -// } -// }); - } catch (Exception e) { - throw ThrowingEx.asRuntime(e); - } - } - private V8FunctionWrapper createResolveConfigFunction(NodeJSWrapper nodeJSWrapper, Map[] outputOptions, Exception[] toThrow) { - return nodeJSWrapper.createNewFunction((receiver, parameters) -> { - try { - try (final V8ObjectWrapper configOptions = parameters.getObject(0)) { - if (configOptions == null) { - toThrow[0] = new IllegalArgumentException("Cannot find or read config file " + this.prettierConfig.getPrettierConfigPath()); - } else { - Map resolvedOptions = new TreeMap<>(V8ObjectUtilsWrapper.toMap(configOptions)); - resolvedOptions.putAll(this.prettierConfig.getOptions()); - outputOptions[0] = resolvedOptions; - } - } - } catch (Exception e) { - toThrow[0] = e; - } - return receiver; - }); - } - - private V8ObjectWrapper createResolveConfigOptionObj(NodeJSWrapper nodeJSWrapper) { - return nodeJSWrapper.createNewObject() - .add("config", this.prettierConfig.getPrettierConfigPath().getAbsolutePath()); - } + ServerProcessInfo prettierRestServer = npmRunServer(); + PrettierRestService restService = new PrettierRestService(prettierRestServer.getBaseUrl()); - private V8ArrayWrapper createResolveConfigParamsArray(NodeJSWrapper nodeJSWrapper, V8ObjectWrapper resolveConfigOption) { - return nodeJSWrapper.createNewArray() - .pushNull() - .push(resolveConfigOption); - } - - private void executeResolution(NodeJSWrapper nodeJSWrapper, Map[] resolvedPrettierOptions, Exception[] toThrow) { - while (resolvedPrettierOptions[0] == null && toThrow[0] == null) { - nodeJSWrapper.handleMessage(); - } - - if (toThrow[0] != null) { - throw ThrowingEx.asRuntime(toThrow[0]); + String prettierConfigOptions = restService.resolveConfig(this.prettierConfig.getPrettierConfigPath(), this.prettierConfig.getOptions()); + return FormatterFunc.Closeable.of(prettierRestServer, input -> restService.format(input, prettierConfigOptions)); + } catch (Exception e) { + throw ThrowingEx.asRuntime(e); } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java index 41b02df6c3..9838eef97b 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java @@ -7,12 +7,11 @@ package com.diffplug.spotless.npm; import javax.annotation.Nonnull; -import java.io.BufferedReader; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; +import java.io.*; import java.net.HttpURLConnection; import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Map; import static java.util.Objects.requireNonNull; @@ -53,19 +52,26 @@ String postJson(String endpoint, String rawJson) throws SimpleRestException { throw new SimpleRestResponseException(status, con.getResponseMessage(), "Unexpected response status code."); } - BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); - String inputLine; - StringBuilder content = new StringBuilder(); - while ((inputLine = in.readLine()) != null) { - content.append(inputLine); - } - in.close(); - return content.toString(); + String response = readResponse(con, StandardCharsets.UTF_8); + return response; } catch (IOException e) { throw new SimpleRestIOException(e); } } + private String readResponse(HttpURLConnection con, Charset charset) throws IOException { + String encoding = con.getContentEncoding(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int numRead; + try(BufferedInputStream input = new BufferedInputStream(con.getInputStream())) { + while ((numRead = input.read(buffer)) != -1) { + output.write(buffer, 0, numRead); + } + return output.toString(charset.name()); + } + } + static abstract class SimpleRestException extends RuntimeException { public SimpleRestException() { diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json index 5921bad6a0..acd47c82c2 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json @@ -1,6 +1,9 @@ { "name": "spotless-prettier-formatter-step", - "version": "1.0.0", + "version": "2.0.0", + "scripts": { + "start": "node serve.js" + }, "devDependencies": { ${devDependencies}, "express": "4.17.1" diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js new file mode 100644 index 0000000000..bd423bb11b --- /dev/null +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js @@ -0,0 +1,85 @@ +const express = require("express"); +const app = express(); +app.use(express.json()); + +const prettier = require("prettier"); + +const fs = require("fs"); + +var listener = app.listen(() => { + console.log("Server running on port " + listener.address().port); + fs.writeFile('server.port.tmp', '' + listener.address().port, function (err) { + if (err) { + return console.log(err); + } else { + fs.rename('server.port.tmp', 'server.port', function (err) { + if (err) { + return console.log(err); + } + }); // try to be as atomic as possible + } + }); +}); + +app.post("/prettier/config-options", (req, res) => { + var config_data = req.body; + var prettier_config_path = config_data.prettier_config_path; + var prettier_config_options = config_data.prettier_config_options || {}; + + if (prettier_config_path) { + prettier.resolveConfig(undefined, {config: prettier_config_path}) + .then(options => { + var mergedConfigOptions = mergeConfigOptions(options, prettier_config_options); + res.json(mergedConfigOptions); + }) + .catch(reason => res.status(501).send('Exception while resolving config_file_path: ' + reason)); + return; + } + res.json(prettier_config_options); +}); + +app.post("/prettier/format", (req, res) => { + var format_data = req.body; + + var formatted_file_content = prettier.format(format_data.file_content, format_data.config_options); + res.set('Content-Type', 'text/plain') + res.send(formatted_file_content); +}); + +var mergeConfigOptions = function (resolved_config_options, config_options) { + if (resolved_config_options !== undefined && config_options !== undefined) { + return extend(resolved_config_options, config_options); + } + if (resolved_config_options === undefined) { + return config_options; + } + if (config_options === undefined) { + return resolved_config_options; + } +} + +var extend = function () { + + // Variables + var extended = {}; + var i = 0; + var length = arguments.length; + + // Merge the object into the extended object + var merge = function (obj) { + for (var prop in obj) { + if (Object.prototype.hasOwnProperty.call(obj, prop)) { + extended[prop] = obj[prop]; + } + } + }; + + // Loop through each object and conduct a merge + for (; i < length; i++) { + var obj = arguments[i]; + merge(obj); + } + + return extended; + +}; \ No newline at end of file From 372107ade6ed5f851fb4907d71a7eabf2a420cf6 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 16:00:32 +0200 Subject: [PATCH 05/43] use native node approach for tsfmt namely start a node-server and rest-call into that node-server --- .../spotless/npm/SimpleJsonWriter.java | 4 ++ .../spotless/npm/TsFmtFormatterStep.java | 70 +++---------------- .../spotless/npm/TsFmtRestService.java | 31 ++++++++ .../diffplug/spotless/npm/TsFmtResult.java | 41 ----------- .../diffplug/spotless/npm/tsfmt-package.json | 8 ++- .../com/diffplug/spotless/npm/tsfmt-serve.js | 54 ++++++++++++++ 6 files changed, 104 insertions(+), 104 deletions(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/npm/TsFmtResult.java create mode 100644 lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java index 4346ba88c9..f01ef54562 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java @@ -66,6 +66,10 @@ String toJsonString() { return "{\n" + valueString + "\n}"; } + RawJsonValue toRawJsonValue() { + return RawJsonValue.asRawJson(toJsonString()); + } + private String jsonEscape(Object val) { requireNonNull(val); if (val instanceof RawJsonValue) { 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 8180ce589f..8125a66440 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java @@ -80,7 +80,8 @@ public State(String stepName, Map versions, Provisioner provisio provisioner, new NpmConfig( replaceDevDependencies(readFileFromClasspath(TsFmtFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-package.json"), new TreeMap<>(versions)), - "typescript-formatter"), + "typescript-formatter", + readFileFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-serve.js")), buildDir, npm); this.buildDir = requireNonNull(buildDir); @@ -91,69 +92,16 @@ public State(String stepName, Map versions, Provisioner provisio @Override @Nonnull public FormatterFunc createFormatterFunc() { + try { + Map tsFmtOptions = unifyOptions(); - Map tsFmtOptions = unifyOptions(); + ServerProcessInfo tsfmtRestServer = npmRunServer(); + TsFmtRestService restService = new TsFmtRestService(tsfmtRestServer.getBaseUrl()); - final NodeJSWrapper nodeJSWrapper = nodeJSWrapper(); - final V8ObjectWrapper tsFmt = nodeJSWrapper.require(nodeModulePath()); - final V8ObjectWrapper formatterOptions = nodeJSWrapper.createNewObject(tsFmtOptions); - - final TsFmtResult[] tsFmtResult = new TsFmtResult[1]; - final Exception[] toThrow = new Exception[1]; - - V8FunctionWrapper formatResultCallback = createFormatResultCallback(nodeJSWrapper, tsFmtResult, toThrow); - - /* var result = { - fileName: fileName, - settings: formatSettings, - message: message, <-- string - error: error, <-- boolean - src: content, - dest: formattedCode, <-- result + return FormatterFunc.Closeable.of(tsfmtRestServer, input -> restService.format(input, tsFmtOptions)); + } catch (Exception e) { + throw ThrowingEx.asRuntime(e); } - */ - return FormatterFunc.Closeable.of(() -> { - asList(formatResultCallback, formatterOptions, tsFmt, nodeJSWrapper).forEach(ReflectiveObjectWrapper::release); - }, input -> { - tsFmtResult[0] = null; - - // function processString(fileName: string, content: string, opts: Options): Promise { - - try ( - V8ArrayWrapper processStringArgs = nodeJSWrapper.createNewArray("spotless-format-string.ts", input, formatterOptions); - V8ObjectWrapper promise = tsFmt.executeObjectFunction("processString", processStringArgs); - V8ArrayWrapper callbacks = nodeJSWrapper.createNewArray(formatResultCallback)) { - - promise.executeVoidFunction("then", callbacks); - - while (tsFmtResult[0] == null && toThrow[0] == null) { - nodeJSWrapper.handleMessage(); - } - - if (toThrow[0] != null) { - throw ThrowingEx.asRuntime(toThrow[0]); - } - - if (tsFmtResult[0] == null) { - throw new IllegalStateException("should never happen"); - } - if (tsFmtResult[0].isError()) { - throw new RuntimeException(tsFmtResult[0].getMessage()); - } - return tsFmtResult[0].getFormatted(); - } - }); - } - - private V8FunctionWrapper createFormatResultCallback(NodeJSWrapper nodeJSWrapper, TsFmtResult[] outputTsFmtResult, Exception[] toThrow) { - return nodeJSWrapper.createNewFunction((receiver, parameters) -> { - try (final V8ObjectWrapper result = parameters.getObject(0)) { - outputTsFmtResult[0] = new TsFmtResult(result.getString("message"), result.getBoolean("error"), result.getString("dest")); - } catch (Exception e) { - toThrow[0] = e; - } - return receiver; - }); } private Map unifyOptions() { diff --git a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java new file mode 100644 index 0000000000..d68f274f18 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 Ergon Informatik AG + * Merkurstrasse 43, 8032 Zuerich, Switzerland + * All rights reserved. + */ + +package com.diffplug.spotless.npm; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class TsFmtRestService { + + private final SimpleRestClient restClient; + + TsFmtRestService(String baseUrl) { + this.restClient = SimpleRestClient.forBaseUrl(baseUrl); + } + + + public String format(String fileContent, Map configOptions) { + Map jsonProperties = new LinkedHashMap<>(); + jsonProperties.put("file_content", fileContent); + if (configOptions != null && !configOptions.isEmpty()) { + jsonProperties.put("config_options", SimpleJsonWriter.of(configOptions).toRawJsonValue()); + } + + return restClient.postJson("/tsfmt/format", jsonProperties); + } + +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtResult.java b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtResult.java deleted file mode 100644 index c43f4963f3..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtResult.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.npm; - -class TsFmtResult { - - private final String message; - private final Boolean error; - private final String formatted; - - TsFmtResult(String message, Boolean error, String formatted) { - this.message = message; - this.error = error; - this.formatted = formatted; - } - - String getMessage() { - return message; - } - - Boolean isError() { - return error; - } - - String getFormatted() { - return formatted; - } -} diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json index 5fddb1105f..0bb5476d80 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json @@ -1,8 +1,12 @@ { "name": "spotless-tsfmt-formatter-step", - "version": "1.0.0", + "version": "2.0.0", + "scripts": { + "start": "node serve.js" + }, "devDependencies": { -${devDependencies} +${devDependencies}, + "express": "4.17.1" }, "dependencies": {}, "engines": { diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js new file mode 100644 index 0000000000..a29a86690b --- /dev/null +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js @@ -0,0 +1,54 @@ +const express = require("express"); +const app = express(); +app.use(express.json()); + +const tsfmt = require("typescript-formatter"); + +const fs = require("fs"); + +var listener = app.listen(() => { + console.log("Server running on port " + listener.address().port); + fs.writeFile('server.port.tmp', '' + listener.address().port, function (err) { + if (err) { + return console.log(err); + } else { + fs.rename('server.port.tmp', 'server.port', function (err) { + if (err) { + return console.log(err); + } + }); // try to be as atomic as possible + } + }); +}); + +app.post("/tsfmt/format", (req, res) => { + var format_data = req.body; + + console.log("formatData", format_data) + + tsfmt.processString("spotless-format-string.ts", format_data.file_content, format_data.config_options).then((resultMap) => { + console.log("resultMap", resultMap); + /* + export interface ResultMap { + [fileName: string]: Result; + } + + export interface Result { + fileName: string; + settings: ts.FormatCodeSettings | null; + message: string; + error: boolean; + src: string; + dest: string; + } + */ + // result contains 'message' (String), 'error' (boolean), 'dest' (String) => formatted + if (resultMap.error !== undefined && resultMap.error) { + res.status(400).send(resultmap.message); + return; + } + res.set('Content-Type', 'text/plain') + res.send(resultMap.dest); + + }); +}); From 51da6fe3652e28a968c79c4b558168041eece4b5 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 16:25:34 +0200 Subject: [PATCH 06/43] remove dependency to j2v8 --- .../diffplug/spotless/npm/NodeJSWrapper.java | 132 -------- .../diffplug/spotless/npm/NodeJsGlobal.java | 93 ------ .../npm/NpmFormatterStepStateBase.java | 19 +- .../spotless/npm/PrettierFormatterStep.java | 7 +- .../spotless/npm/PrettierRestService.java | 6 +- .../com/diffplug/spotless/npm/Reflective.java | 302 ------------------ .../spotless/npm/ReflectiveObjectWrapper.java | 76 ----- .../spotless/npm/SimpleJsonWriter.java | 4 +- .../spotless/npm/TsFmtFormatterStep.java | 12 +- .../diffplug/spotless/npm/V8ArrayWrapper.java | 51 --- .../spotless/npm/V8FunctionWrapper.java | 58 ---- .../spotless/npm/V8ObjectUtilsWrapper.java | 35 -- .../spotless/npm/V8ObjectWrapper.java | 89 ------ .../spotless/SpotlessExtensionBase.java | 4 - .../npm/NodeJsNativeDoubleLoadTest.java | 53 --- 15 files changed, 16 insertions(+), 925 deletions(-) delete mode 100644 lib/src/main/java/com/diffplug/spotless/npm/NodeJSWrapper.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/npm/NodeJsGlobal.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/npm/Reflective.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/npm/ReflectiveObjectWrapper.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/npm/V8ArrayWrapper.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/npm/V8FunctionWrapper.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/npm/V8ObjectUtilsWrapper.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/npm/V8ObjectWrapper.java delete mode 100644 testlib/src/test/java/com/diffplug/spotless/npm/NodeJsNativeDoubleLoadTest.java diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeJSWrapper.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeJSWrapper.java deleted file mode 100644 index 67c121baed..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/npm/NodeJSWrapper.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.npm; - -import java.io.File; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -import com.diffplug.spotless.ThrowingEx; - -class NodeJSWrapper extends ReflectiveObjectWrapper { - - public static final String V8_RUNTIME_CLASS = "com.eclipsesource.v8.V8"; - public static final String V8_VALUE_CLASS = "com.eclipsesource.v8.V8Value"; - - public static final String WRAPPED_CLASS = "com.eclipsesource.v8.NodeJS"; - - private static final Set alreadySetup = new HashSet<>(); - - public NodeJSWrapper(ClassLoader classLoader) { - super(Reflective.withClassLoader(classLoader), - reflective -> { - if (alreadySetup.add(classLoader)) { - // the bridge to node.js needs a .dll/.so/.dylib which gets loaded through System.load - // the problem is that when the JVM loads that DLL, it is bound to the specific classloader that called System.load - // no other classloaders have access to it, and if any other classloader tries to load it, you get an error - // - // ...but, you can copy that DLL as many times as you want, and each classloader can load its own copy of the DLL, and - // that is fine - // - // ...but the in order to do that, we have to manually load the DLL per classloader ourselves, which involves some - // especially hacky reflection into J2V8 *and* JVM internal - // - // so this is bad code, but it fixes our problem, and so far we don't have a better way... - - // here we get the name of the DLL within the jar, and we get our own copy of it on disk - String resource = (String) reflective.invokeStaticMethodPrivate("com.eclipsesource.v8.LibraryLoader", "computeLibraryFullName"); - File file = NodeJsGlobal.sharedLibs.nextDynamicLib(classLoader, resource); - - // ideally, we would call System.load, but the JVM does some tricky stuff to - // figure out who actually called this, and it realizes it was Reflective, which lives - // outside the J2V8 classloader, so System.load doesn't work. Soooo, we have to dig - // into JVM internals and manually tell it "this class from J2V8 called you" - Class libraryLoaderClass = ThrowingEx.get(() -> classLoader.loadClass("com.eclipsesource.v8.LibraryLoader")); - reflective.invokeStaticMethodPrivate("java.lang.ClassLoader", "loadLibrary0", libraryLoaderClass, file); - - // and now we set the flag in J2V8 which says "the DLL is loaded, don't load it again" - reflective.staticFieldPrivate("com.eclipsesource.v8.V8", "nativeLibraryLoaded", true); - } - return reflective.invokeStaticMethod(WRAPPED_CLASS, "createNodeJS"); - }); - } - - public V8ObjectWrapper require(File npmModulePath) { - Objects.requireNonNull(npmModulePath); - Object v8Object = invoke("require", npmModulePath); - return new V8ObjectWrapper(reflective(), v8Object); - } - - public V8ObjectWrapper createNewObject() { - Object v8Object = reflective().invokeConstructor(V8ObjectWrapper.WRAPPED_CLASS, nodeJsRuntime()); - V8ObjectWrapper objectWrapper = new V8ObjectWrapper(reflective(), v8Object); - return objectWrapper; - } - - public V8ObjectWrapper createNewObject(Map values) { - Objects.requireNonNull(values); - V8ObjectWrapper obj = createNewObject(); - values.forEach(obj::add); - return obj; - } - - public V8ArrayWrapper createNewArray(Object... elements) { - final V8ArrayWrapper v8ArrayWrapper = this.createNewArray(); - for (Object element : elements) { - v8ArrayWrapper.push(element); - } - return v8ArrayWrapper; - } - - public V8ArrayWrapper createNewArray() { - Object v8Array = reflective().invokeConstructor(V8ArrayWrapper.WRAPPED_CLASS, nodeJsRuntime()); - V8ArrayWrapper arrayWrapper = new V8ArrayWrapper(reflective(), v8Array); - return arrayWrapper; - } - - public V8FunctionWrapper createNewFunction(V8FunctionWrapper.WrappedJavaCallback callback) { - Object v8Function = reflective().invokeConstructor(V8FunctionWrapper.WRAPPED_CLASS, - reflective().typed( - V8_RUNTIME_CLASS, - nodeJsRuntime()), - reflective().typed( - V8FunctionWrapper.CALLBACK_WRAPPED_CLASS, - V8FunctionWrapper.proxiedCallback(callback, reflective()))); - V8FunctionWrapper functionWrapper = new V8FunctionWrapper(reflective(), v8Function); - return functionWrapper; - } - - public void handleMessage() { - invoke("handleMessage"); - } - - private Object nodeJsRuntime() { - return invoke("getRuntime"); - } - - public Object v8NullValue(Object value) { - if (value == null) { - return reflective().staticField(V8_VALUE_CLASS, "NULL"); - } - return value; - } - - public boolean isV8NullValue(Object v8Object) { - return reflective().staticField(V8_VALUE_CLASS, "NULL") == v8Object; - } -} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeJsGlobal.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeJsGlobal.java deleted file mode 100644 index ad2a5ff428..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/npm/NodeJsGlobal.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.npm; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; -import java.util.stream.IntStream; - -import com.diffplug.spotless.LineEnding; -import com.diffplug.spotless.ThrowingEx; - -/** Shared config acress the NodeJS steps. */ -public class NodeJsGlobal { - static SharedLibFolder sharedLibs; - - static { - sharedLibs = new SharedLibFolder( - ThrowingEx.get(() -> Files.createTempDirectory("spotless-nodejs"))); - sharedLibs.root.deleteOnExit(); - } - - /** - * All of the NodeJS steps need to extract a bridge DLL for node. By default this is - * a random location, but you can set it to be anywhere. - */ - public static void setSharedLibFolder(File sharedLibFolder) { - sharedLibs = new SharedLibFolder(sharedLibFolder.toPath()); - } - - static class SharedLibFolder { - private final File root; - - private SharedLibFolder(Path root) { - this.root = ThrowingEx.get(() -> root.toFile().getCanonicalFile()); - } - - static final int MAX_CLASSLOADERS_PER_CLEAN = 1_000; - - synchronized File nextDynamicLib(ClassLoader loader, String resource) { - // find a new unique file - Optional nextLibOpt = IntStream.range(0, MAX_CLASSLOADERS_PER_CLEAN) - .mapToObj(i -> new File(root, i + "_" + resource)) - .filter(file -> !file.exists()) - .findFirst(); - if (!nextLibOpt.isPresent()) { - throw new IllegalArgumentException("Overflow, delete the spotless nodeJs cache: " + root); - } - File nextLib = nextLibOpt.get(); - // copy the dll to it - try { - Files.createDirectories(nextLib.getParentFile().toPath()); - try (FileOutputStream fileOut = new FileOutputStream(nextLib); - InputStream resourceIn = loader.loadClass("com.eclipsesource.v8.LibraryLoader").getResourceAsStream("/" + resource)) { - byte[] buf = new byte[0x1000]; - while (true) { - int r = resourceIn.read(buf); - if (r == -1) { - break; - } - fileOut.write(buf, 0, r); - } - } - } catch (IOException | ClassNotFoundException e) { - throw ThrowingEx.asRuntime(e); - } - // make sure it is executable (on unix) - if (LineEnding.PLATFORM_NATIVE.str().equals("\n")) { - ThrowingEx.run(() -> { - Runtime.getRuntime().exec(new String[]{"chmod", "755", nextLib.getAbsolutePath()}).waitFor(); //$NON-NLS-1$ - }); - } - return nextLib; - } - } -} 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 6f6f00d8fd..79b53b869b 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -33,9 +33,7 @@ abstract class NpmFormatterStepStateBase implements Serializable { - private static final long serialVersionUID = -5849375492831208496L; - - private final JarState jarState; + private static final long serialVersionUID = 1460749955865959948L; @SuppressWarnings("unused") private final FileSignature nodeModulesSignature; @@ -49,10 +47,9 @@ abstract class NpmFormatterStepStateBase implements Serializable { private final String stepName; - protected NpmFormatterStepStateBase(String stepName, Provisioner provisioner, NpmConfig npmConfig, File buildDir, @Nullable File npm) throws IOException { + protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, File buildDir, @Nullable File npm) throws IOException { this.stepName = requireNonNull(stepName); this.npmConfig = requireNonNull(npmConfig); - this.jarState = JarState.from(j2v8MavenCoordinate(), requireNonNull(provisioner)); this.npmExecutable = resolveNpm(npm); this.nodeModulesDir = prepareNodeServer(buildDir); @@ -132,18 +129,6 @@ private static File resolveNpm(@Nullable File npm) { .orElseThrow(() -> new IllegalStateException("cannot automatically determine npm executable and none was specifically supplied!"))); } - protected NodeJSWrapper nodeJSWrapper() { - return new NodeJSWrapper(this.jarState.getClassLoader()); - } - - protected File nodeModulePath() { - return new File(new File(this.nodeModulesDir, "node_modules"), this.npmConfig.getNpmModule()); - } - - static String j2v8MavenCoordinate() { - return "com.eclipsesource.j2v8:j2v8_" + PlatformInfo.normalizedOSName() + "_" + PlatformInfo.normalizedArchName() + ":4.6.0"; - } - protected static String readFileFromClasspath(Class clazz, String name) { ByteArrayOutputStream output = new ByteArrayOutputStream(); try (InputStream input = clazz.getResourceAsStream(name)) { 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 4249ec7f56..c8dbf75d58 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -53,18 +53,17 @@ public static FormatterStep create(Map devDependencies, Provisio requireNonNull(provisioner); requireNonNull(buildDir); return FormatterStep.createLazy(NAME, - () -> new State(NAME, devDependencies, provisioner, buildDir, npm, prettierConfig), + () -> new State(NAME, devDependencies, buildDir, npm, prettierConfig), State::createFormatterFunc); } public static class State extends NpmFormatterStepStateBase implements Serializable { - private static final long serialVersionUID = -3811104513825329168L; + private static final long serialVersionUID = -539537027004745812L; private final PrettierConfig prettierConfig; - State(String stepName, Map devDependencies, Provisioner provisioner, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) throws IOException { + State(String stepName, Map devDependencies, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) throws IOException { super(stepName, - provisioner, new NpmConfig( replaceDevDependencies( readFileFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-package.json"), diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java index f74f5519df..046f4958e7 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java @@ -6,6 +6,8 @@ package com.diffplug.spotless.npm; +import com.diffplug.spotless.npm.SimpleJsonWriter.RawJsonValue; + import java.io.File; import java.util.LinkedHashMap; import java.util.Map; @@ -24,7 +26,7 @@ public String resolveConfig(File prettierConfigPath, Map prettie jsonProperties.put("prettier_config_path", prettierConfigPath.getAbsolutePath()); } if (prettierConfigOptions != null) { - jsonProperties.put("prettier_config_options", SimpleJsonWriter.RawJsonValue.asRawJson(SimpleJsonWriter.of(prettierConfigOptions).toJsonString())); + jsonProperties.put("prettier_config_options", SimpleJsonWriter.of(prettierConfigOptions).toRawJsonValue()); } return restClient.postJson("/prettier/config-options", jsonProperties); @@ -34,7 +36,7 @@ public String format(String fileContent, String configOptionsJsonString) { Map jsonProperties = new LinkedHashMap<>(); jsonProperties.put("file_content", fileContent); if (configOptionsJsonString != null) { - jsonProperties.put("config_options", SimpleJsonWriter.RawJsonValue.asRawJson(configOptionsJsonString)); + jsonProperties.put("config_options", RawJsonValue.wrap(configOptionsJsonString)); } return restClient.postJson("/prettier/format", jsonProperties); diff --git a/lib/src/main/java/com/diffplug/spotless/npm/Reflective.java b/lib/src/main/java/com/diffplug/spotless/npm/Reflective.java deleted file mode 100644 index 3c65ea0492..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/npm/Reflective.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.npm; - -import static java.util.Objects.requireNonNull; - -import java.lang.reflect.*; -import java.util.Arrays; -import java.util.Objects; -import java.util.StringJoiner; - -import com.diffplug.spotless.ThrowingEx; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -class Reflective { - private final ClassLoader classLoader; - - private Reflective(ClassLoader classLoader) { - this.classLoader = requireNonNull(classLoader); - } - - static Reflective withClassLoader(ClassLoader classLoader) { - return new Reflective(classLoader); - } - - Class clazz(String className) { - try { - return this.classLoader.loadClass(className); - } catch (ClassNotFoundException e) { - throw new ReflectiveException(e); - } - } - - private Method staticMethod(String className, String methodName, Object... parameters) { - try { - final Class clazz = clazz(className); - return clazz.getDeclaredMethod(methodName, types(parameters)); - } catch (NoSuchMethodException e) { - throw new ReflectiveException(e); - } - } - - Object invokeStaticMethod(String className, String methodName, Object... parameters) { - try { - Method m = staticMethod(className, methodName, parameters); - return m.invoke(m.getDeclaringClass(), parameters); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new ReflectiveException(e); - } - } - - @SuppressFBWarnings("DP_DO_INSIDE_DO_PRIVILEGED") - Object invokeStaticMethodPrivate(String className, String methodName, Object... parameters) { - try { - Method m = staticMethod(className, methodName, parameters); - m.setAccessible(true); - return m.invoke(m.getDeclaringClass(), parameters); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new ReflectiveException(e); - } - } - - private Class[] types(TypedValue[] typedValues) { - return Arrays.stream(typedValues) - .map(TypedValue::getClazz) - .toArray(Class[]::new); - } - - Class[] types(Object[] arguments) { - return Arrays.stream(arguments) - .map(Object::getClass) - .toArray(Class[]::new); - } - - Object invokeMethod(Object target, String methodName, Object... parameters) { - Method m = method(target, clazz(target), methodName, parameters); - try { - return m.invoke(target, parameters); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new ReflectiveException(e); - } - } - - Object invokeMethod(Object target, String methodName, TypedValue... parameters) { - Method m = method(target, clazz(target), methodName, parameters); - try { - return m.invoke(target, objects(parameters)); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new ReflectiveException(e); - } - } - - private Method method(Object target, Class clazz, String methodName, Object[] parameters) { - try { - final Method method = findMatchingMethod(clazz, methodName, parameters); - return method; - } catch (NoSuchMethodException e) { - if (clazz.getSuperclass() != null) { - return method(target, clazz.getSuperclass(), methodName, parameters); - } else { - throw new ReflectiveException("Could not find method " + methodName + " with parameters " + Arrays.toString(parameters) + " on object " + target, e); - } - } - } - - private Method method(Object target, Class clazz, String methodName, TypedValue[] parameters) { - try { - final Method method = findMatchingMethod(clazz, methodName, parameters); - return method; - } catch (NoSuchMethodException e) { - if (clazz.getSuperclass() != null) { - return method(target, clazz.getSuperclass(), methodName, parameters); - } else { - throw new ReflectiveException("Could not find method " + methodName + " with parameters " + Arrays.toString(parameters) + " on object " + target, e); - } - } - } - - private Method findMatchingMethod(Class clazz, String methodName, Object[] parameters) throws NoSuchMethodException { - final Class[] origTypes = types(parameters); - try { - return clazz.getDeclaredMethod(methodName, origTypes); - } catch (NoSuchMethodException e) { - // try with primitives - final Class[] primitives = autoUnbox(origTypes); - try { - return clazz.getDeclaredMethod(methodName, primitives); - } catch (NoSuchMethodException e1) { - // didn't work either - throw e; - } - } - } - - private Method findMatchingMethod(Class clazz, String methodName, TypedValue[] parameters) throws NoSuchMethodException { - return clazz.getDeclaredMethod(methodName, types(parameters)); - } - - private Class[] autoUnbox(Class[] possiblyBoxed) { - return Arrays.stream(possiblyBoxed) - .map(clazz -> { - try { - return (Class) this.staticField(clazz, "TYPE"); - } catch (ReflectiveException e) { - // no primitive type, just keeping current clazz - return clazz; - } - }).toArray(Class[]::new); - } - - private Class clazz(Object target) { - return target.getClass(); - } - - Object invokeConstructor(String className, TypedValue... parameters) { - try { - final Constructor constructor = constructor(className, parameters); - return constructor.newInstance(objects(parameters)); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new ReflectiveException(e); - } - } - - private Object[] objects(TypedValue[] parameters) { - return Arrays.stream(parameters) - .map(TypedValue::getObj) - .toArray(); - } - - Object invokeConstructor(String className, Object... parameters) { - try { - final Constructor constructor = constructor(className, parameters); - return constructor.newInstance(parameters); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new ReflectiveException(e); - } - } - - private Constructor constructor(String className, TypedValue[] parameters) { - try { - final Class clazz = clazz(className); - final Constructor constructor = clazz.getDeclaredConstructor(types(parameters)); - return constructor; - } catch (NoSuchMethodException e) { - throw new ReflectiveException(e); - } - } - - private Constructor constructor(String className, Object[] parameters) { - try { - final Class clazz = clazz(className); - final Constructor constructor = clazz.getDeclaredConstructor(types(parameters)); - return constructor; - } catch (NoSuchMethodException e) { - throw new ReflectiveException(e); - } - } - - Object createDynamicProxy(InvocationHandler invocationHandler, String... interfaceNames) { - Class[] clazzes = Arrays.stream(interfaceNames) - .map(this::clazz) - .toArray(Class[]::new); - return Proxy.newProxyInstance(this.classLoader, clazzes, invocationHandler); - } - - Object staticField(String className, String fieldName) { - final Class clazz = clazz(className); - return staticField(clazz, fieldName); - } - - private Object staticField(Class clazz, String fieldName) { - try { - return clazz.getDeclaredField(fieldName).get(clazz); - } catch (IllegalAccessException | NoSuchFieldException e) { - throw new ReflectiveException(e); - } - } - - @SuppressFBWarnings("DP_DO_INSIDE_DO_PRIVILEGED") - void staticFieldPrivate(String className, String fieldName, boolean newValue) { - Class clazz = clazz(className); - try { - Field field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - field.setBoolean(null, newValue); - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { - throw ThrowingEx.asRuntime(e); - } - } - - TypedValue typed(String className, Object obj) { - return new TypedValue(clazz(className), obj); - } - - public static class TypedValue { - private final Class clazz; - private final Object obj; - - public TypedValue(Class clazz, Object obj) { - this.clazz = requireNonNull(clazz); - this.obj = requireNonNull(obj); - } - - public Class getClazz() { - return clazz; - } - - public Object getObj() { - return obj; - } - - @Override - public String toString() { - return new StringJoiner(", ", TypedValue.class.getSimpleName() + "[", "]") - .add("clazz=" + clazz) - .add("obj=" + obj) - .toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - TypedValue that = (TypedValue) o; - return Objects.equals(clazz, that.clazz) && - Objects.equals(obj, that.obj); - } - - @Override - public int hashCode() { - return Objects.hash(clazz, obj); - } - } - - public static class ReflectiveException extends RuntimeException { - private static final long serialVersionUID = -5764607170953013791L; - - public ReflectiveException(String message, Throwable cause) { - super(message, cause); - } - - public ReflectiveException(Throwable cause) { - super(cause); - } - } -} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/ReflectiveObjectWrapper.java b/lib/src/main/java/com/diffplug/spotless/npm/ReflectiveObjectWrapper.java deleted file mode 100644 index 3dfffc8c94..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/npm/ReflectiveObjectWrapper.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.npm; - -import static java.util.Objects.requireNonNull; - -import java.util.Objects; -import java.util.function.Function; - -abstract class ReflectiveObjectWrapper implements AutoCloseable { - - private final Object wrappedObj; - private final Reflective reflective; - - public ReflectiveObjectWrapper(Reflective reflective, Object wrappedObj) { - this.reflective = requireNonNull(reflective); - this.wrappedObj = requireNonNull(wrappedObj); - } - - public ReflectiveObjectWrapper(Reflective reflective, Function wrappedObjSupplier) { - this(reflective, wrappedObjSupplier.apply(reflective)); - } - - protected Reflective reflective() { - return this.reflective; - } - - protected Object wrappedObj() { - return this.wrappedObj; - } - - protected Object invoke(String methodName, Object... parameters) { - return reflective().invokeMethod(wrappedObj(), methodName, parameters); - } - - protected Object invoke(String methodName, Reflective.TypedValue... parameters) { - return reflective().invokeMethod(wrappedObj(), methodName, parameters); - } - - public void release() { - invoke("release"); - } - - @Override - public void close() throws Exception { - release(); - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof ReflectiveObjectWrapper)) - return false; - ReflectiveObjectWrapper that = (ReflectiveObjectWrapper) o; - return Objects.equals(wrappedObj, that.wrappedObj) && Objects.equals(getClass(), that.getClass()); - } - - @Override - public int hashCode() { - return Objects.hash(wrappedObj, getClass()); - } -} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java index f01ef54562..d42b0eefeb 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java @@ -67,7 +67,7 @@ String toJsonString() { } RawJsonValue toRawJsonValue() { - return RawJsonValue.asRawJson(toJsonString()); + return RawJsonValue.wrap(toJsonString()); } private String jsonEscape(Object val) { @@ -165,7 +165,7 @@ private RawJsonValue(String rawJson) { this.rawJson = requireNonNull(rawJson); } - static RawJsonValue asRawJson(String rawJson) { + static RawJsonValue wrap(String rawJson) { return new RawJsonValue(rawJson); } 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 8125a66440..6e4914b9fc 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java @@ -15,7 +15,6 @@ */ package com.diffplug.spotless.npm; -import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; import java.io.File; @@ -43,7 +42,7 @@ public static FormatterStep create(Map versions, Provisioner pro requireNonNull(provisioner); requireNonNull(buildDir); return FormatterStep.createLazy(NAME, - () -> new State(NAME, versions, provisioner, buildDir, npm, configFile, inlineTsFmtSettings), + () -> new State(NAME, versions, buildDir, npm, configFile, inlineTsFmtSettings), State::createFormatterFunc); } @@ -61,7 +60,7 @@ public static Map defaultDevDependenciesWithTsFmt(String typescr public static class State extends NpmFormatterStepStateBase implements Serializable { - private static final long serialVersionUID = -3811104513825329168L; + private static final long serialVersionUID = -3789035117345809383L; private final TreeMap inlineTsFmtSettings; @@ -71,13 +70,12 @@ public static class State extends NpmFormatterStepStateBase implements Serializa private final TypedTsFmtConfigFile configFile; @Deprecated - public State(String stepName, Provisioner provisioner, File buildDir, @Nullable File npm, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map inlineTsFmtSettings) throws IOException { - this(stepName, defaultDevDependencies(), provisioner, buildDir, npm, configFile, inlineTsFmtSettings); + public State(String stepName, File buildDir, @Nullable File npm, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map inlineTsFmtSettings) throws IOException { + this(stepName, defaultDevDependencies(), buildDir, npm, configFile, inlineTsFmtSettings); } - public State(String stepName, Map versions, Provisioner provisioner, File buildDir, @Nullable File npm, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map inlineTsFmtSettings) throws IOException { + public State(String stepName, Map versions, File buildDir, @Nullable File npm, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map inlineTsFmtSettings) throws IOException { super(stepName, - provisioner, new NpmConfig( replaceDevDependencies(readFileFromClasspath(TsFmtFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-package.json"), new TreeMap<>(versions)), "typescript-formatter", diff --git a/lib/src/main/java/com/diffplug/spotless/npm/V8ArrayWrapper.java b/lib/src/main/java/com/diffplug/spotless/npm/V8ArrayWrapper.java deleted file mode 100644 index 0d26df0b64..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/npm/V8ArrayWrapper.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.npm; - -public class V8ArrayWrapper extends ReflectiveObjectWrapper { - - public static final String WRAPPED_CLASS = "com.eclipsesource.v8.V8Array"; - - public V8ArrayWrapper(Reflective reflective, Object v8Array) { - super(reflective, v8Array); - } - - public V8ArrayWrapper push(Object object) { - if (object instanceof ReflectiveObjectWrapper) { - ReflectiveObjectWrapper objectWrapper = (ReflectiveObjectWrapper) object; - object = objectWrapper.wrappedObj(); - } - if (reflective().clazz(NodeJSWrapper.V8_VALUE_CLASS).isAssignableFrom(object.getClass())) { - invoke("push", reflective().typed(NodeJSWrapper.V8_VALUE_CLASS, object)); - } else { - invoke("push", object); - } - return this; - } - - public V8ArrayWrapper pushNull() { - invoke("pushNull"); - return this; - } - - public V8ObjectWrapper getObject(Integer index) { - Object v8Object = invoke("getObject", index); - if (v8Object == null) { - return null; - } - return new V8ObjectWrapper(this.reflective(), v8Object); - } -} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/V8FunctionWrapper.java b/lib/src/main/java/com/diffplug/spotless/npm/V8FunctionWrapper.java deleted file mode 100644 index 36a9804854..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/npm/V8FunctionWrapper.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.npm; - -import java.lang.reflect.Method; - -class V8FunctionWrapper extends ReflectiveObjectWrapper { - - public static final String WRAPPED_CLASS = "com.eclipsesource.v8.V8Function"; - public static final String CALLBACK_WRAPPED_CLASS = "com.eclipsesource.v8.JavaCallback"; - - public V8FunctionWrapper(Reflective reflective, Object v8Function) { - super(reflective, v8Function); - } - - public static Object proxiedCallback(WrappedJavaCallback callback, Reflective reflective) { - Object proxy = reflective.createDynamicProxy((proxyInstance, method, args) -> { - if (isCallbackFunction(reflective, method, args)) { - V8ObjectWrapper receiver = new V8ObjectWrapper(reflective, args[0]); - V8ArrayWrapper parameters = new V8ArrayWrapper(reflective, args[1]); - return callback.invoke(receiver, parameters); - } - return null; - }, CALLBACK_WRAPPED_CLASS); - return reflective.clazz(CALLBACK_WRAPPED_CLASS).cast(proxy); - } - - private static boolean isCallbackFunction(Reflective reflective, Method method, Object[] args) { - if (!"invoke".equals(method.getName())) { - return false; - } - final Class[] types = reflective.types(args); - if (types.length != 2) { - return false; - } - - return V8ObjectWrapper.WRAPPED_CLASS.equals(types[0].getName()) && - V8ArrayWrapper.WRAPPED_CLASS.equals(types[1].getName()); - } - - @FunctionalInterface - public interface WrappedJavaCallback { - Object invoke(V8ObjectWrapper receiver, V8ArrayWrapper parameters); - } -} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/V8ObjectUtilsWrapper.java b/lib/src/main/java/com/diffplug/spotless/npm/V8ObjectUtilsWrapper.java deleted file mode 100644 index af7448c748..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/npm/V8ObjectUtilsWrapper.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.npm; - -import static java.util.Objects.requireNonNull; - -import java.util.Map; - -class V8ObjectUtilsWrapper { - - public static final String WRAPPED_CLASS = "com.eclipsesource.v8.utils.V8ObjectUtils"; - - public static Map toMap(final V8ObjectWrapper object) { - requireNonNull(object); - - final Reflective reflective = object.reflective(); - - @SuppressWarnings("unchecked") - final Map map = (Map) reflective.invokeStaticMethod(WRAPPED_CLASS, "toMap", object.wrappedObj()); - return map; - } -} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/V8ObjectWrapper.java b/lib/src/main/java/com/diffplug/spotless/npm/V8ObjectWrapper.java deleted file mode 100644 index 917e25c222..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/npm/V8ObjectWrapper.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.npm; - -import java.util.Optional; - -class V8ObjectWrapper extends ReflectiveObjectWrapper { - - public static final String WRAPPED_CLASS = "com.eclipsesource.v8.V8Object"; - - public V8ObjectWrapper(Reflective reflective, Object v8Object) { - super(reflective, v8Object); - } - - public V8ObjectWrapper add(String name, Object value) { - invoke("add", name, value); - return this; - } - - public void executeVoidFunction(String functionName, V8ArrayWrapper params) { - invoke("executeVoidFunction", functionName, params.wrappedObj()); - } - - public V8ObjectWrapper executeObjectFunction(String functionName, V8ArrayWrapper params) { - Object returnV8Obj = invoke("executeObjectFunction", functionName, params.wrappedObj()); - return new V8ObjectWrapper(reflective(), returnV8Obj); - } - - public String executeStringFunction(String functionName, V8ArrayWrapper params) { - String returnValue = (String) invoke("executeStringFunction", functionName, params.wrappedObj()); - return returnValue; - } - - public String getString(String name) { - return (String) invoke("getString", name); - } - - public Optional getOptionalString(String name) { - String result = null; - try { - result = getString(name); - } catch (RuntimeException e) { - // ignore - } - return Optional.ofNullable(result); - } - - public boolean getBoolean(String name) { - return (boolean) invoke("getBoolean", name); - } - - public Optional getOptionalBoolean(String name) { - Boolean result = null; - try { - result = getBoolean(name); - } catch (RuntimeException e) { - // ignore - } - return Optional.ofNullable(result); - } - - public int getInteger(String name) { - return (int) invoke("getInteger", name); - } - - public Optional getOptionalInteger(String name) { - Integer result = null; - try { - result = getInteger(name); - } catch (RuntimeException e) { - // ignore - } - return Optional.ofNullable(result); - } - -} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionBase.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionBase.java index 7cbc45ffb7..35ad80ed3f 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionBase.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionBase.java @@ -17,7 +17,6 @@ import static java.util.Objects.requireNonNull; -import java.io.File; import java.lang.reflect.Constructor; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -32,7 +31,6 @@ import com.diffplug.common.base.Errors; import com.diffplug.spotless.LineEnding; -import com.diffplug.spotless.npm.NodeJsGlobal; public abstract class SpotlessExtensionBase { final Project project; @@ -54,8 +52,6 @@ public SpotlessExtensionBase(Project project) { if (registerDependenciesTask == null) { registerDependenciesTask = project.getRootProject().getTasks().create(RegisterDependenciesTask.TASK_NAME, RegisterDependenciesTask.class); registerDependenciesTask.setup(); - // set where the nodejs runtime will put its temp dlls - NodeJsGlobal.setSharedLibFolder(new File(project.getBuildDir(), "spotless-nodejs-cache")); } this.registerDependenciesTask = registerDependenciesTask; } diff --git a/testlib/src/test/java/com/diffplug/spotless/npm/NodeJsNativeDoubleLoadTest.java b/testlib/src/test/java/com/diffplug/spotless/npm/NodeJsNativeDoubleLoadTest.java deleted file mode 100644 index 33ec041cf2..0000000000 --- a/testlib/src/test/java/com/diffplug/spotless/npm/NodeJsNativeDoubleLoadTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.spotless.npm; - -import java.util.Optional; - -import org.assertj.core.api.Assertions; -import org.junit.Test; - -import com.diffplug.common.collect.ImmutableMap; -import com.diffplug.spotless.JarState; -import com.diffplug.spotless.TestProvisioner; - -public class NodeJsNativeDoubleLoadTest { - @Test - public void inMultipleClassLoaders() throws Exception { - JarState state = JarState.from(NpmFormatterStepStateBase.j2v8MavenCoordinate(), TestProvisioner.mavenCentral()); - ClassLoader loader1 = state.getClassLoader(1); - ClassLoader loader2 = state.getClassLoader(2); - createAndTestWrapper(loader1); - createAndTestWrapper(loader2); - } - - @Test - public void multipleTimesInOneClassLoader() throws Exception { - JarState state = JarState.from(NpmFormatterStepStateBase.j2v8MavenCoordinate(), TestProvisioner.mavenCentral()); - ClassLoader loader3 = state.getClassLoader(3); - createAndTestWrapper(loader3); - createAndTestWrapper(loader3); - } - - private void createAndTestWrapper(ClassLoader loader) throws Exception { - try (NodeJSWrapper node = new NodeJSWrapper(loader)) { - V8ObjectWrapper object = node.createNewObject(ImmutableMap.of("a", 1)); - Optional value = object.getOptionalInteger("a"); - Assertions.assertThat(value).hasValue(1); - object.release(); - } - } -} From 9999bdd9ce6964319276152603d3e38f6e505774 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 16:31:08 +0200 Subject: [PATCH 07/43] spotlessApply --- .../com/diffplug/spotless/npm/NpmConfig.java | 3 +- .../npm/NpmFormatterStepStateBase.java | 362 +++++++++--------- .../spotless/npm/PrettierFormatterStep.java | 133 +++---- .../spotless/npm/PrettierRestService.java | 75 ++-- .../spotless/npm/SimpleJsonWriter.java | 76 ++-- .../spotless/npm/SimpleRestClient.java | 228 +++++------ .../spotless/npm/TsFmtRestService.java | 42 +- .../diffplug/spotless/npm/prettier-serve.js | 2 +- .../com/diffplug/spotless/npm/tsfmt-serve.js | 2 +- 9 files changed, 476 insertions(+), 447 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java index b752d10f5d..789da2b34d 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java @@ -15,9 +15,10 @@ */ package com.diffplug.spotless.npm; -import javax.annotation.Nonnull; import java.io.Serializable; +import javax.annotation.Nonnull; + class NpmConfig implements Serializable { private static final long serialVersionUID = -7660089232952131272L; 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 79b53b869b..83a6e7db23 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -15,10 +15,8 @@ */ package com.diffplug.spotless.npm; -import com.diffplug.spotless.*; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import static java.util.Objects.requireNonNull; -import javax.annotation.Nullable; import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -29,184 +27,188 @@ import java.util.Optional; import java.util.concurrent.TimeoutException; -import static java.util.Objects.requireNonNull; +import javax.annotation.Nullable; + +import com.diffplug.spotless.*; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; abstract class NpmFormatterStepStateBase implements Serializable { - private static final long serialVersionUID = 1460749955865959948L; - - @SuppressWarnings("unused") - private final FileSignature nodeModulesSignature; - - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - public final transient File nodeModulesDir; - - private final transient File npmExecutable; - - private final NpmConfig npmConfig; - - private final String stepName; - - protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, File buildDir, @Nullable File npm) throws IOException { - this.stepName = requireNonNull(stepName); - this.npmConfig = requireNonNull(npmConfig); - this.npmExecutable = resolveNpm(npm); - - this.nodeModulesDir = prepareNodeServer(buildDir); - this.nodeModulesSignature = FileSignature.signAsList(this.nodeModulesDir); - } - - private File prepareNodeServer(File buildDir) throws IOException { - File targetDir = new File(buildDir, "spotless-node-modules-" + stepName); - if (!targetDir.exists()) { - if (!targetDir.mkdirs()) { - throw new IOException("cannot create temp dir for node modules at " + targetDir); - } - } - writeContentToFile(targetDir, "package.json", this.npmConfig.getPackageJsonContent()); - writeContentToFile(targetDir, "serve.js", this.npmConfig.getServeScriptContent()); - runNpmInstall(targetDir); - return targetDir; - } - - private void writeContentToFile(File targetDir, String s, String packageJsonContent) throws IOException { - File packageJsonFile = new File(targetDir, s); - Files.write(packageJsonFile.toPath(), packageJsonContent.getBytes(StandardCharsets.UTF_8)); - } - - private void runNpmInstall(File npmProjectDir) throws IOException { - Process npmInstall = new ProcessBuilder() - .inheritIO() - .directory(npmProjectDir) - .command(this.npmExecutable.getAbsolutePath(), "install", "--no-audit", "--no-package-lock") - .start(); - try { - if (npmInstall.waitFor() != 0) { - throw new IOException("Creating npm modules failed with exit code: " + npmInstall.exitValue()); - } - } catch (InterruptedException e) { - throw new IOException("Running npm install was interrupted.", e); - } - } - - protected ServerProcessInfo npmRunServer() throws ServerStartException { - try { - Process server = new ProcessBuilder() - .inheritIO() -// .redirectError(new File("/Users/simschla/tmp/npmerror.log")) -// .redirectOutput(new File("/Users/simschla/tmp/npmout.log")) - .directory(this.nodeModulesDir) - .command(this.npmExecutable.getAbsolutePath(), "start") - .start(); - - File serverPortFile = new File(this.nodeModulesDir, "server.port"); - final long startedAt = System.currentTimeMillis(); - while (!serverPortFile.exists() || !serverPortFile.canRead()) { - // wait for at most 10 seconds - if ((System.currentTimeMillis() - startedAt) > (10 * 1000L)) { - // forcibly end the server process - try { - server.destroyForcibly(); - } catch (Throwable t) { - // log this? - } - throw new TimeoutException("The server did not startup in the requested time frame of 10 seconds."); - } - } - // readPort from file - - // read the server.port file for resulting port - String serverPort = readFile(serverPortFile).trim(); - return new ServerProcessInfo(server, serverPort, serverPortFile); - } catch (IOException | TimeoutException e) { - throw new ServerStartException(e); - } - } - - private static File resolveNpm(@Nullable File npm) { - return Optional.ofNullable(npm) - .orElseGet(() -> NpmExecutableResolver.tryFind() - .orElseThrow(() -> new IllegalStateException("cannot automatically determine npm executable and none was specifically supplied!"))); - } - - protected static String readFileFromClasspath(Class clazz, String name) { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - try (InputStream input = clazz.getResourceAsStream(name)) { - byte[] buffer = new byte[1024]; - int numRead; - while ((numRead = input.read(buffer)) != -1) { - output.write(buffer, 0, numRead); - } - return output.toString(StandardCharsets.UTF_8.name()); - } catch (IOException e) { - throw ThrowingEx.asRuntime(e); - } - } - - protected static String readFile(File file) { - try { - return String.join("\n", Files.readAllLines(file.toPath())); - } catch (IOException e) { - throw ThrowingEx.asRuntime(e); - } - } - - protected static String replaceDevDependencies(String template, Map devDependencies) { - StringBuilder builder = new StringBuilder(); - Iterator> entryIter = devDependencies.entrySet().iterator(); - while (entryIter.hasNext()) { - Map.Entry entry = entryIter.next(); - builder.append("\t\t\""); - builder.append(entry.getKey()); - builder.append("\": \""); - builder.append(entry.getValue()); - builder.append("\""); - if (entryIter.hasNext()) { - builder.append(",\n"); - } - } - return replacePlaceholders(template, Collections.singletonMap("devDependencies", builder.toString())); - } - - private static String replacePlaceholders(String template, Map replacements) { - String result = template; - for (Entry entry : replacements.entrySet()) { - result = result.replaceAll("\\Q${" + entry.getKey() + "}\\E", entry.getValue()); - } - return result; - } - - public abstract FormatterFunc createFormatterFunc(); - - protected class ServerProcessInfo implements AutoCloseable { - private final Process server; - private final String serverPort; - private final File serverPortFile; - - public ServerProcessInfo(Process server, String serverPort, File serverPortFile) { - this.server = server; - this.serverPort = serverPort; - this.serverPortFile = serverPortFile; - } - - public String getBaseUrl() { - return "http://127.0.0.1:" + this.serverPort; - } - - @Override - public void close() throws Exception { - if (serverPortFile.exists()) { - serverPortFile.delete(); - } - if (this.server.isAlive()) { - this.server.destroy(); - } - } - } - - protected class ServerStartException extends RuntimeException { - public ServerStartException(Throwable cause) { - super(cause); - } - } + private static final long serialVersionUID = 1460749955865959948L; + + @SuppressWarnings("unused") + private final FileSignature nodeModulesSignature; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + public final transient File nodeModulesDir; + + private final transient File npmExecutable; + + private final NpmConfig npmConfig; + + private final String stepName; + + protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, File buildDir, @Nullable File npm) throws IOException { + this.stepName = requireNonNull(stepName); + this.npmConfig = requireNonNull(npmConfig); + this.npmExecutable = resolveNpm(npm); + + this.nodeModulesDir = prepareNodeServer(buildDir); + this.nodeModulesSignature = FileSignature.signAsList(this.nodeModulesDir); + } + + private File prepareNodeServer(File buildDir) throws IOException { + File targetDir = new File(buildDir, "spotless-node-modules-" + stepName); + if (!targetDir.exists()) { + if (!targetDir.mkdirs()) { + throw new IOException("cannot create temp dir for node modules at " + targetDir); + } + } + writeContentToFile(targetDir, "package.json", this.npmConfig.getPackageJsonContent()); + writeContentToFile(targetDir, "serve.js", this.npmConfig.getServeScriptContent()); + runNpmInstall(targetDir); + return targetDir; + } + + private void writeContentToFile(File targetDir, String s, String packageJsonContent) throws IOException { + File packageJsonFile = new File(targetDir, s); + Files.write(packageJsonFile.toPath(), packageJsonContent.getBytes(StandardCharsets.UTF_8)); + } + + private void runNpmInstall(File npmProjectDir) throws IOException { + Process npmInstall = new ProcessBuilder() + .inheritIO() + .directory(npmProjectDir) + .command(this.npmExecutable.getAbsolutePath(), "install", "--no-audit", "--no-package-lock") + .start(); + try { + if (npmInstall.waitFor() != 0) { + throw new IOException("Creating npm modules failed with exit code: " + npmInstall.exitValue()); + } + } catch (InterruptedException e) { + throw new IOException("Running npm install was interrupted.", e); + } + } + + protected ServerProcessInfo npmRunServer() throws ServerStartException { + try { + Process server = new ProcessBuilder() + .inheritIO() + // .redirectError(new File("/Users/simschla/tmp/npmerror.log")) + // .redirectOutput(new File("/Users/simschla/tmp/npmout.log")) + .directory(this.nodeModulesDir) + .command(this.npmExecutable.getAbsolutePath(), "start") + .start(); + + File serverPortFile = new File(this.nodeModulesDir, "server.port"); + final long startedAt = System.currentTimeMillis(); + while (!serverPortFile.exists() || !serverPortFile.canRead()) { + // wait for at most 10 seconds + if ((System.currentTimeMillis() - startedAt) > (10 * 1000L)) { + // forcibly end the server process + try { + server.destroyForcibly(); + } catch (Throwable t) { + // log this? + } + throw new TimeoutException("The server did not startup in the requested time frame of 10 seconds."); + } + } + // readPort from file + + // read the server.port file for resulting port + String serverPort = readFile(serverPortFile).trim(); + return new ServerProcessInfo(server, serverPort, serverPortFile); + } catch (IOException | TimeoutException e) { + throw new ServerStartException(e); + } + } + + private static File resolveNpm(@Nullable File npm) { + return Optional.ofNullable(npm) + .orElseGet(() -> NpmExecutableResolver.tryFind() + .orElseThrow(() -> new IllegalStateException("cannot automatically determine npm executable and none was specifically supplied!"))); + } + + protected static String readFileFromClasspath(Class clazz, String name) { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try (InputStream input = clazz.getResourceAsStream(name)) { + byte[] buffer = new byte[1024]; + int numRead; + while ((numRead = input.read(buffer)) != -1) { + output.write(buffer, 0, numRead); + } + return output.toString(StandardCharsets.UTF_8.name()); + } catch (IOException e) { + throw ThrowingEx.asRuntime(e); + } + } + + protected static String readFile(File file) { + try { + return String.join("\n", Files.readAllLines(file.toPath())); + } catch (IOException e) { + throw ThrowingEx.asRuntime(e); + } + } + + protected static String replaceDevDependencies(String template, Map devDependencies) { + StringBuilder builder = new StringBuilder(); + Iterator> entryIter = devDependencies.entrySet().iterator(); + while (entryIter.hasNext()) { + Map.Entry entry = entryIter.next(); + builder.append("\t\t\""); + builder.append(entry.getKey()); + builder.append("\": \""); + builder.append(entry.getValue()); + builder.append("\""); + if (entryIter.hasNext()) { + builder.append(",\n"); + } + } + return replacePlaceholders(template, Collections.singletonMap("devDependencies", builder.toString())); + } + + private static String replacePlaceholders(String template, Map replacements) { + String result = template; + for (Entry entry : replacements.entrySet()) { + result = result.replaceAll("\\Q${" + entry.getKey() + "}\\E", entry.getValue()); + } + return result; + } + + public abstract FormatterFunc createFormatterFunc(); + + protected class ServerProcessInfo implements AutoCloseable { + private final Process server; + private final String serverPort; + private final File serverPortFile; + + public ServerProcessInfo(Process server, String serverPort, File serverPortFile) { + this.server = server; + this.serverPort = serverPort; + this.serverPortFile = serverPortFile; + } + + public String getBaseUrl() { + return "http://127.0.0.1:" + this.serverPort; + } + + @Override + public void close() throws Exception { + if (serverPortFile.exists()) { + serverPortFile.delete(); + } + if (this.server.isAlive()) { + this.server.destroy(); + } + } + } + + protected class ServerStartException extends RuntimeException { + public ServerStartException(Throwable cause) { + super(cause); + } + } } 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 c8dbf75d58..80a711424c 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -15,13 +15,8 @@ */ package com.diffplug.spotless.npm; -import com.diffplug.spotless.FormatterFunc; -import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.Provisioner; -import com.diffplug.spotless.ThrowingEx; +import static java.util.Objects.requireNonNull; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.io.Serializable; @@ -29,67 +24,73 @@ import java.util.Map; import java.util.TreeMap; -import static java.util.Objects.requireNonNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.Provisioner; +import com.diffplug.spotless.ThrowingEx; public class PrettierFormatterStep { - public static final String NAME = "prettier-format"; - - public static final Map defaultDevDependencies() { - return defaultDevDependenciesWithPrettier("1.16.4"); - } - - public static final Map defaultDevDependenciesWithPrettier(String version) { - return Collections.singletonMap("prettier", version); - } - - @Deprecated - public static FormatterStep create(Provisioner provisioner, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) { - return create(defaultDevDependencies(), provisioner, buildDir, npm, prettierConfig); - } - - public static FormatterStep create(Map devDependencies, Provisioner provisioner, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) { - requireNonNull(devDependencies); - requireNonNull(provisioner); - requireNonNull(buildDir); - return FormatterStep.createLazy(NAME, - () -> new State(NAME, devDependencies, buildDir, npm, prettierConfig), - State::createFormatterFunc); - } - - public static class State extends NpmFormatterStepStateBase implements Serializable { - - private static final long serialVersionUID = -539537027004745812L; - private final PrettierConfig prettierConfig; - - State(String stepName, Map devDependencies, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) throws IOException { - super(stepName, - new NpmConfig( - replaceDevDependencies( - readFileFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-package.json"), - new TreeMap<>(devDependencies)), - "prettier", - readFileFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-serve.js")), - buildDir, - npm); - this.prettierConfig = requireNonNull(prettierConfig); - } - - @Override - @Nonnull - public FormatterFunc createFormatterFunc() { - - try { - - ServerProcessInfo prettierRestServer = npmRunServer(); - PrettierRestService restService = new PrettierRestService(prettierRestServer.getBaseUrl()); - - String prettierConfigOptions = restService.resolveConfig(this.prettierConfig.getPrettierConfigPath(), this.prettierConfig.getOptions()); - return FormatterFunc.Closeable.of(prettierRestServer, input -> restService.format(input, prettierConfigOptions)); - } catch (Exception e) { - throw ThrowingEx.asRuntime(e); - } - } - - } + public static final String NAME = "prettier-format"; + + public static final Map defaultDevDependencies() { + return defaultDevDependenciesWithPrettier("1.16.4"); + } + + public static final Map defaultDevDependenciesWithPrettier(String version) { + return Collections.singletonMap("prettier", version); + } + + @Deprecated + public static FormatterStep create(Provisioner provisioner, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) { + return create(defaultDevDependencies(), provisioner, buildDir, npm, prettierConfig); + } + + public static FormatterStep create(Map devDependencies, Provisioner provisioner, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) { + requireNonNull(devDependencies); + requireNonNull(provisioner); + requireNonNull(buildDir); + return FormatterStep.createLazy(NAME, + () -> new State(NAME, devDependencies, buildDir, npm, prettierConfig), + State::createFormatterFunc); + } + + public static class State extends NpmFormatterStepStateBase implements Serializable { + + private static final long serialVersionUID = -539537027004745812L; + private final PrettierConfig prettierConfig; + + State(String stepName, Map devDependencies, File buildDir, @Nullable File npm, PrettierConfig prettierConfig) throws IOException { + super(stepName, + new NpmConfig( + replaceDevDependencies( + readFileFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-package.json"), + new TreeMap<>(devDependencies)), + "prettier", + readFileFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-serve.js")), + buildDir, + npm); + this.prettierConfig = requireNonNull(prettierConfig); + } + + @Override + @Nonnull + public FormatterFunc createFormatterFunc() { + + try { + + ServerProcessInfo prettierRestServer = npmRunServer(); + PrettierRestService restService = new PrettierRestService(prettierRestServer.getBaseUrl()); + + String prettierConfigOptions = restService.resolveConfig(this.prettierConfig.getPrettierConfigPath(), this.prettierConfig.getOptions()); + return FormatterFunc.Closeable.of(prettierRestServer, input -> restService.format(input, prettierConfigOptions)); + } catch (Exception e) { + throw ThrowingEx.asRuntime(e); + } + } + + } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java index 046f4958e7..d28a55dd27 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java @@ -1,45 +1,54 @@ /* - * Copyright (c) 2020 Ergon Informatik AG - * Merkurstrasse 43, 8032 Zuerich, Switzerland - * All rights reserved. + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ - package com.diffplug.spotless.npm; -import com.diffplug.spotless.npm.SimpleJsonWriter.RawJsonValue; - import java.io.File; import java.util.LinkedHashMap; import java.util.Map; +import com.diffplug.spotless.npm.SimpleJsonWriter.RawJsonValue; + public class PrettierRestService { - private final SimpleRestClient restClient; - - PrettierRestService(String baseUrl) { - this.restClient = SimpleRestClient.forBaseUrl(baseUrl); - } - - public String resolveConfig(File prettierConfigPath, Map prettierConfigOptions) { - Map jsonProperties = new LinkedHashMap<>(); - if (prettierConfigPath != null) { - jsonProperties.put("prettier_config_path", prettierConfigPath.getAbsolutePath()); - } - if (prettierConfigOptions != null) { - jsonProperties.put("prettier_config_options", SimpleJsonWriter.of(prettierConfigOptions).toRawJsonValue()); - - } - return restClient.postJson("/prettier/config-options", jsonProperties); - } - - public String format(String fileContent, String configOptionsJsonString) { - Map jsonProperties = new LinkedHashMap<>(); - jsonProperties.put("file_content", fileContent); - if (configOptionsJsonString != null) { - jsonProperties.put("config_options", RawJsonValue.wrap(configOptionsJsonString)); - } - - return restClient.postJson("/prettier/format", jsonProperties); - } + private final SimpleRestClient restClient; + + PrettierRestService(String baseUrl) { + this.restClient = SimpleRestClient.forBaseUrl(baseUrl); + } + + public String resolveConfig(File prettierConfigPath, Map prettierConfigOptions) { + Map jsonProperties = new LinkedHashMap<>(); + if (prettierConfigPath != null) { + jsonProperties.put("prettier_config_path", prettierConfigPath.getAbsolutePath()); + } + if (prettierConfigOptions != null) { + jsonProperties.put("prettier_config_options", SimpleJsonWriter.of(prettierConfigOptions).toRawJsonValue()); + + } + return restClient.postJson("/prettier/config-options", jsonProperties); + } + + public String format(String fileContent, String configOptionsJsonString) { + Map jsonProperties = new LinkedHashMap<>(); + jsonProperties.put("file_content", fileContent); + if (configOptionsJsonString != null) { + jsonProperties.put("config_options", RawJsonValue.wrap(configOptionsJsonString)); + } + + return restClient.postJson("/prettier/format", jsonProperties); + } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java index d42b0eefeb..31c06ad03f 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java @@ -53,7 +53,7 @@ SimpleJsonWriter put(String name, Object value) { private void verifyValues(Map values) { if (values.values() .stream() - .anyMatch(val -> !(val instanceof String || val instanceof RawJsonValue|| val instanceof Number || val instanceof Boolean))) { + .anyMatch(val -> !(val instanceof String || val instanceof RawJsonValue || val instanceof Number || val instanceof Boolean))) { throw new IllegalArgumentException("Only values of type 'String', 'RawJsonValue', 'Number' and 'Boolean' are supported. You provided: " + values.values()); } } @@ -91,47 +91,47 @@ private String jsonEscape(Object val) { escaped.append('"'); char b; char c = 0; - for(int i = 0; i< ((String)val).length();i++) { + for (int i = 0; i < ((String) val).length(); i++) { b = c; c = ((String) val).charAt(i); switch (c) { - case '\"': - escaped.append('\\').append('"'); - break; - case '\n': - escaped.append('\\').append('n'); - break; - case '\r': - escaped.append('\\').append('r'); - break; - case '\t': - escaped.append('\\').append('t'); - break; - case '\b': - escaped.append('\\').append('b'); - break; - case '\f': - escaped.append('\\').append('f'); - break; - case '\\': - escaped.append('\\').append('\\'); - break; - case '/': - if (b == '<') { - escaped.append('\\'); - } + case '\"': + escaped.append('\\').append('"'); + break; + case '\n': + escaped.append('\\').append('n'); + break; + case '\r': + escaped.append('\\').append('r'); + break; + case '\t': + escaped.append('\\').append('t'); + break; + case '\b': + escaped.append('\\').append('b'); + break; + case '\f': + escaped.append('\\').append('f'); + break; + case '\\': + escaped.append('\\').append('\\'); + break; + case '/': + if (b == '<') { + escaped.append('\\'); + } + escaped.append(c); + break; + default: + if (c < ' ' || (c >= '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100')) { + escaped.append('\\').append('u'); + String hexString = Integer.toHexString(c); + escaped.append("0000", 0, 4 - hexString.length()); + escaped.append(hexString); + } else { escaped.append(c); - break; - default: - if (c < ' ' || (c >= '\u0080' && c < '\u00a0') - || (c >= '\u2000' && c < '\u2100')) { - escaped.append('\\').append('u'); - String hexString = Integer.toHexString(c); - escaped.append("0000", 0, 4 - hexString.length()); - escaped.append(hexString); - } else { - escaped.append(c); - } + } } } escaped.append('"'); diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java index 9838eef97b..d8358500a5 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java @@ -1,12 +1,22 @@ /* - * Copyright (c) 2020 Ergon Informatik AG - * Merkurstrasse 43, 8032 Zuerich, Switzerland - * All rights reserved. + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ - package com.diffplug.spotless.npm; -import javax.annotation.Nonnull; +import static java.util.Objects.requireNonNull; + import java.io.*; import java.net.HttpURLConnection; import java.net.URL; @@ -14,111 +24,109 @@ import java.nio.charset.StandardCharsets; import java.util.Map; -import static java.util.Objects.requireNonNull; +import javax.annotation.Nonnull; class SimpleRestClient { - private final String baseUrl; - - private SimpleRestClient(String baseUrl) { - this.baseUrl = requireNonNull(baseUrl); - } - - static SimpleRestClient forBaseUrl(String baseUrl) { - return new SimpleRestClient(baseUrl); - } - - String postJson(String endpoint, Map jsonParams) throws SimpleRestException { - final SimpleJsonWriter jsonWriter = SimpleJsonWriter.of(jsonParams); - final String jsonString = jsonWriter.toJsonString(); - - return postJson(endpoint, jsonString); - } - - String postJson(String endpoint, String rawJson) throws SimpleRestException { - try { - URL url = new URL(this.baseUrl + endpoint); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setRequestMethod("POST"); - con.setRequestProperty("Content-Type", "application/json"); - con.setDoOutput(true); - DataOutputStream out = new DataOutputStream(con.getOutputStream()); - out.writeBytes(rawJson); - out.flush(); - out.close(); - - int status = con.getResponseCode(); - - if (status != 200) { - throw new SimpleRestResponseException(status, con.getResponseMessage(), "Unexpected response status code."); - } - - String response = readResponse(con, StandardCharsets.UTF_8); - return response; - } catch (IOException e) { - throw new SimpleRestIOException(e); - } - } - - private String readResponse(HttpURLConnection con, Charset charset) throws IOException { - String encoding = con.getContentEncoding(); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int numRead; - try(BufferedInputStream input = new BufferedInputStream(con.getInputStream())) { - while ((numRead = input.read(buffer)) != -1) { - output.write(buffer, 0, numRead); - } - return output.toString(charset.name()); - } - } - - - static abstract class SimpleRestException extends RuntimeException { - public SimpleRestException() { - } - - public SimpleRestException(Throwable cause) { - super(cause); - } - } - - static class SimpleRestResponseException extends SimpleRestException { - private final int statusCode; - - private final String responseMessage; - - private final String exceptionMessage; - - public SimpleRestResponseException(int statusCode, String responseMessage, String exceptionmessage) { - this.statusCode = statusCode; - this.responseMessage = responseMessage; - this.exceptionMessage = exceptionmessage; - } - - @Nonnull - public int getStatusCode() { - return statusCode; - } - - @Nonnull - public String getResponseMessage() { - return responseMessage; - } - - @Nonnull - public String getExceptionMessage() { - return exceptionMessage; - } - - @Override - public String getMessage() { - return String.format("%s: %s (%s)", getStatusCode(), getResponseMessage(), getExceptionMessage()); - } - } - - static class SimpleRestIOException extends SimpleRestException { - public SimpleRestIOException(Throwable cause) { - super(cause); - } - } + private final String baseUrl; + + private SimpleRestClient(String baseUrl) { + this.baseUrl = requireNonNull(baseUrl); + } + + static SimpleRestClient forBaseUrl(String baseUrl) { + return new SimpleRestClient(baseUrl); + } + + String postJson(String endpoint, Map jsonParams) throws SimpleRestException { + final SimpleJsonWriter jsonWriter = SimpleJsonWriter.of(jsonParams); + final String jsonString = jsonWriter.toJsonString(); + + return postJson(endpoint, jsonString); + } + + String postJson(String endpoint, String rawJson) throws SimpleRestException { + try { + URL url = new URL(this.baseUrl + endpoint); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/json"); + con.setDoOutput(true); + DataOutputStream out = new DataOutputStream(con.getOutputStream()); + out.writeBytes(rawJson); + out.flush(); + out.close(); + + int status = con.getResponseCode(); + + if (status != 200) { + throw new SimpleRestResponseException(status, con.getResponseMessage(), "Unexpected response status code."); + } + + String response = readResponse(con, StandardCharsets.UTF_8); + return response; + } catch (IOException e) { + throw new SimpleRestIOException(e); + } + } + + private String readResponse(HttpURLConnection con, Charset charset) throws IOException { + String encoding = con.getContentEncoding(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int numRead; + try (BufferedInputStream input = new BufferedInputStream(con.getInputStream())) { + while ((numRead = input.read(buffer)) != -1) { + output.write(buffer, 0, numRead); + } + return output.toString(charset.name()); + } + } + + static abstract class SimpleRestException extends RuntimeException { + public SimpleRestException() {} + + public SimpleRestException(Throwable cause) { + super(cause); + } + } + + static class SimpleRestResponseException extends SimpleRestException { + private final int statusCode; + + private final String responseMessage; + + private final String exceptionMessage; + + public SimpleRestResponseException(int statusCode, String responseMessage, String exceptionmessage) { + this.statusCode = statusCode; + this.responseMessage = responseMessage; + this.exceptionMessage = exceptionmessage; + } + + @Nonnull + public int getStatusCode() { + return statusCode; + } + + @Nonnull + public String getResponseMessage() { + return responseMessage; + } + + @Nonnull + public String getExceptionMessage() { + return exceptionMessage; + } + + @Override + public String getMessage() { + return String.format("%s: %s (%s)", getStatusCode(), getResponseMessage(), getExceptionMessage()); + } + } + + static class SimpleRestIOException extends SimpleRestException { + public SimpleRestIOException(Throwable cause) { + super(cause); + } + } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java index d68f274f18..0022b33556 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java @@ -1,9 +1,18 @@ /* - * Copyright (c) 2020 Ergon Informatik AG - * Merkurstrasse 43, 8032 Zuerich, Switzerland - * All rights reserved. + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ - package com.diffplug.spotless.npm; import java.util.LinkedHashMap; @@ -11,21 +20,20 @@ public class TsFmtRestService { - private final SimpleRestClient restClient; - - TsFmtRestService(String baseUrl) { - this.restClient = SimpleRestClient.forBaseUrl(baseUrl); - } + private final SimpleRestClient restClient; + TsFmtRestService(String baseUrl) { + this.restClient = SimpleRestClient.forBaseUrl(baseUrl); + } - public String format(String fileContent, Map configOptions) { - Map jsonProperties = new LinkedHashMap<>(); - jsonProperties.put("file_content", fileContent); - if (configOptions != null && !configOptions.isEmpty()) { - jsonProperties.put("config_options", SimpleJsonWriter.of(configOptions).toRawJsonValue()); - } + public String format(String fileContent, Map configOptions) { + Map jsonProperties = new LinkedHashMap<>(); + jsonProperties.put("file_content", fileContent); + if (configOptions != null && !configOptions.isEmpty()) { + jsonProperties.put("config_options", SimpleJsonWriter.of(configOptions).toRawJsonValue()); + } - return restClient.postJson("/tsfmt/format", jsonProperties); - } + return restClient.postJson("/tsfmt/format", jsonProperties); + } } diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js index bd423bb11b..85d4a6ed1e 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js @@ -1,6 +1,6 @@ const express = require("express"); const app = express(); -app.use(express.json()); +app.use(express.json({ limit: '50mb' })); const prettier = require("prettier"); diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js index a29a86690b..acdcd5ea76 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js @@ -1,6 +1,6 @@ const express = require("express"); const app = express(); -app.use(express.json()); +app.use(express.json({ limit: '50mb' })); const tsfmt = require("typescript-formatter"); From 03fbd98ee09d0288692f2eba7e9ba18d28637127 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 16:42:38 +0200 Subject: [PATCH 08/43] add more details in case npm autodiscovery fails --- .../diffplug/spotless/npm/NpmExecutableResolver.java | 11 +++++++++++ .../spotless/npm/NpmFormatterStepStateBase.java | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmExecutableResolver.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmExecutableResolver.java index cea0984a2f..f36981b39d 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmExecutableResolver.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmExecutableResolver.java @@ -105,4 +105,15 @@ private static Supplier> pathListFromEnvironment(String environme }; } + static String explainMessage() { + return "Spotless tries to find your npm executable automatically. It looks for npm in the following places:\n" + + "- An executable referenced by the java system property 'npm.exec' - if such a system property exists.\n" + + "- The environment variable 'NVM_BIN' - if such an environment variable exists.\n" + + "- The environment variable 'NVM_SYMLINK' - if such an environment variable exists.\n" + + "- The environment variable 'NODE_PATH' - if such an environment variable exists.\n" + + "- In your 'PATH' environment variable\n" + + "\n" + + "If autodiscovery fails for your system, try to set one of the environment variables correctly or\n" + + "try setting the system property 'npm.exec' in the build process to override autodiscovery."; + } } 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 83a6e7db23..66505d5965 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -128,7 +128,7 @@ protected ServerProcessInfo npmRunServer() throws ServerStartException { private static File resolveNpm(@Nullable File npm) { return Optional.ofNullable(npm) .orElseGet(() -> NpmExecutableResolver.tryFind() - .orElseThrow(() -> new IllegalStateException("cannot automatically determine npm executable and none was specifically supplied!"))); + .orElseThrow(() -> new IllegalStateException("Can't automatically determine npm executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage()))); } protected static String readFileFromClasspath(Class clazz, String name) { From 8f88dc20873df91b9fb865a44321196aad8c4a56 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 16:49:05 +0200 Subject: [PATCH 09/43] remove j2v8 reference from readme --- plugin-gradle/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index 700b5c7822..ea07c07777 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -409,7 +409,7 @@ spotless { } ``` -Spotless uses npm to install necessary packages locally. It runs tsfmt using [J2V8](https://github.com/eclipsesource/J2V8) internally after that. +Spotless uses npm to install necessary packages and run the `typescript-formatter` (`tsfmt`) package. @@ -494,7 +494,7 @@ spotless { } ``` -Spotless uses npm to install necessary packages locally. It runs prettier using [J2V8](https://github.com/eclipsesource/J2V8) internally after that. +Spotless uses npm to install necessary packages and run the `prettier` (`tsfmt`) package. From 589940ce10c0330b1a2ae0e00c1f446f4311e599 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 16:55:08 +0200 Subject: [PATCH 10/43] cleanup server startup code --- .../npm/NpmFormatterStepStateBase.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) 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 66505d5965..517c68c2c6 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -93,31 +93,34 @@ private void runNpmInstall(File npmProjectDir) throws IOException { protected ServerProcessInfo npmRunServer() throws ServerStartException { try { + // The npm process will output the randomly selected port of the http server process to 'server.port' file + // so in order to be safe, remove such a file if it exists before starting. + final File serverPortFile = new File(this.nodeModulesDir, "server.port"); + if (serverPortFile.exists()) { + serverPortFile.delete(); + } + // start the http server in node Process server = new ProcessBuilder() .inheritIO() - // .redirectError(new File("/Users/simschla/tmp/npmerror.log")) - // .redirectOutput(new File("/Users/simschla/tmp/npmout.log")) .directory(this.nodeModulesDir) .command(this.npmExecutable.getAbsolutePath(), "start") .start(); - File serverPortFile = new File(this.nodeModulesDir, "server.port"); + // await the readiness of the http server final long startedAt = System.currentTimeMillis(); while (!serverPortFile.exists() || !serverPortFile.canRead()) { - // wait for at most 10 seconds - if ((System.currentTimeMillis() - startedAt) > (10 * 1000L)) { + // wait for at most 60 seconds - if it is not ready by then, something is terribly wrong + if ((System.currentTimeMillis() - startedAt) > (60 * 1000L)) { // forcibly end the server process try { server.destroyForcibly(); } catch (Throwable t) { - // log this? + // ignore } - throw new TimeoutException("The server did not startup in the requested time frame of 10 seconds."); + throw new TimeoutException("The server did not startup in the requested time frame of 60 seconds."); } } - // readPort from file - - // read the server.port file for resulting port + // read the server.port file for resulting port and remember the port for later formatting calls String serverPort = readFile(serverPortFile).trim(); return new ServerProcessInfo(server, serverPort, serverPortFile); } catch (IOException | TimeoutException e) { @@ -180,7 +183,7 @@ private static String replacePlaceholders(String template, Map r public abstract FormatterFunc createFormatterFunc(); - protected class ServerProcessInfo implements AutoCloseable { + protected static class ServerProcessInfo implements AutoCloseable { private final Process server; private final String serverPort; private final File serverPortFile; @@ -206,7 +209,7 @@ public void close() throws Exception { } } - protected class ServerStartException extends RuntimeException { + protected static class ServerStartException extends RuntimeException { public ServerStartException(Throwable cause) { super(cause); } From 48ce207c0eaf8a331ff035642ce7d8929d854699 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 17:04:47 +0200 Subject: [PATCH 11/43] extract file handling to helper class --- .../npm/NpmFormatterStepStateBase.java | 49 ++---------- .../spotless/npm/PrettierFormatterStep.java | 4 +- .../spotless/npm/SimpleResourceHelper.java | 74 +++++++++++++++++++ .../spotless/npm/TsFmtFormatterStep.java | 4 +- 4 files changed, 84 insertions(+), 47 deletions(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/npm/SimpleResourceHelper.java 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 517c68c2c6..83e432def1 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -18,8 +18,6 @@ import static java.util.Objects.requireNonNull; import java.io.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.util.Collections; import java.util.Iterator; import java.util.Map; @@ -60,22 +58,13 @@ protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, File b private File prepareNodeServer(File buildDir) throws IOException { File targetDir = new File(buildDir, "spotless-node-modules-" + stepName); - if (!targetDir.exists()) { - if (!targetDir.mkdirs()) { - throw new IOException("cannot create temp dir for node modules at " + targetDir); - } - } - writeContentToFile(targetDir, "package.json", this.npmConfig.getPackageJsonContent()); - writeContentToFile(targetDir, "serve.js", this.npmConfig.getServeScriptContent()); + SimpleResourceHelper.assertDirectoryExists(targetDir); + SimpleResourceHelper.writeUtf8StringToFile(targetDir, "package.json", this.npmConfig.getPackageJsonContent()); + SimpleResourceHelper.writeUtf8StringToFile(targetDir, "serve.js", this.npmConfig.getServeScriptContent()); runNpmInstall(targetDir); return targetDir; } - private void writeContentToFile(File targetDir, String s, String packageJsonContent) throws IOException { - File packageJsonFile = new File(targetDir, s); - Files.write(packageJsonFile.toPath(), packageJsonContent.getBytes(StandardCharsets.UTF_8)); - } - private void runNpmInstall(File npmProjectDir) throws IOException { Process npmInstall = new ProcessBuilder() .inheritIO() @@ -96,9 +85,7 @@ protected ServerProcessInfo npmRunServer() throws ServerStartException { // The npm process will output the randomly selected port of the http server process to 'server.port' file // so in order to be safe, remove such a file if it exists before starting. final File serverPortFile = new File(this.nodeModulesDir, "server.port"); - if (serverPortFile.exists()) { - serverPortFile.delete(); - } + SimpleResourceHelper.deleteFileIfExists(serverPortFile); // start the http server in node Process server = new ProcessBuilder() .inheritIO() @@ -121,7 +108,7 @@ protected ServerProcessInfo npmRunServer() throws ServerStartException { } } // read the server.port file for resulting port and remember the port for later formatting calls - String serverPort = readFile(serverPortFile).trim(); + String serverPort = SimpleResourceHelper.readUtf8StringFromFile(serverPortFile).trim(); return new ServerProcessInfo(server, serverPort, serverPortFile); } catch (IOException | TimeoutException e) { throw new ServerStartException(e); @@ -134,28 +121,6 @@ private static File resolveNpm(@Nullable File npm) { .orElseThrow(() -> new IllegalStateException("Can't automatically determine npm executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage()))); } - protected static String readFileFromClasspath(Class clazz, String name) { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - try (InputStream input = clazz.getResourceAsStream(name)) { - byte[] buffer = new byte[1024]; - int numRead; - while ((numRead = input.read(buffer)) != -1) { - output.write(buffer, 0, numRead); - } - return output.toString(StandardCharsets.UTF_8.name()); - } catch (IOException e) { - throw ThrowingEx.asRuntime(e); - } - } - - protected static String readFile(File file) { - try { - return String.join("\n", Files.readAllLines(file.toPath())); - } catch (IOException e) { - throw ThrowingEx.asRuntime(e); - } - } - protected static String replaceDevDependencies(String template, Map devDependencies) { StringBuilder builder = new StringBuilder(); Iterator> entryIter = devDependencies.entrySet().iterator(); @@ -200,9 +165,7 @@ public String getBaseUrl() { @Override public void close() throws Exception { - if (serverPortFile.exists()) { - serverPortFile.delete(); - } + SimpleResourceHelper.deleteFileIfExists(serverPortFile); if (this.server.isAlive()) { this.server.destroy(); } 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 80a711424c..38799a0db1 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -67,10 +67,10 @@ public static class State extends NpmFormatterStepStateBase implements Serializa super(stepName, new NpmConfig( replaceDevDependencies( - readFileFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-package.json"), + SimpleResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-package.json"), new TreeMap<>(devDependencies)), "prettier", - readFileFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-serve.js")), + SimpleResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-serve.js")), buildDir, npm); this.prettierConfig = requireNonNull(prettierConfig); diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleResourceHelper.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleResourceHelper.java new file mode 100644 index 0000000000..e2a1b1328e --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleResourceHelper.java @@ -0,0 +1,74 @@ +/* + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.npm; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +import com.diffplug.spotless.ThrowingEx; + +final class SimpleResourceHelper { + private SimpleResourceHelper() { + // no instance required + } + + static void writeUtf8StringToFile(File targetDir, String fileName, String packageJsonContent) throws IOException { + File packageJsonFile = new File(targetDir, fileName); + Files.write(packageJsonFile.toPath(), packageJsonContent.getBytes(StandardCharsets.UTF_8)); + } + + static void deleteFileIfExists(File file) throws IOException { + if (file.exists()) { + if (!file.delete()) { + throw new IOException("Failed to delete " + file); + } + } + } + + static String readUtf8StringFromClasspath(Class clazz, String resourceName) { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try (InputStream input = clazz.getResourceAsStream(resourceName)) { + byte[] buffer = new byte[1024]; + int numRead; + while ((numRead = input.read(buffer)) != -1) { + output.write(buffer, 0, numRead); + } + return output.toString(StandardCharsets.UTF_8.name()); + } catch (IOException e) { + throw ThrowingEx.asRuntime(e); + } + } + + static String readUtf8StringFromFile(File file) { + try { + return String.join("\n", Files.readAllLines(file.toPath())); + } catch (IOException e) { + throw ThrowingEx.asRuntime(e); + } + } + + static void assertDirectoryExists(File directory) throws IOException { + if (!directory.exists()) { + if (!directory.mkdirs()) { + throw new IOException("cannot create temp dir for node modules at " + directory); + } + } + } +} 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 6e4914b9fc..f8114e8934 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java @@ -77,9 +77,9 @@ public State(String stepName, File buildDir, @Nullable File npm, @Nullable Typed public State(String stepName, Map versions, File buildDir, @Nullable File npm, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map inlineTsFmtSettings) throws IOException { super(stepName, new NpmConfig( - replaceDevDependencies(readFileFromClasspath(TsFmtFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-package.json"), new TreeMap<>(versions)), + replaceDevDependencies(SimpleResourceHelper.readUtf8StringFromClasspath(TsFmtFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-package.json"), new TreeMap<>(versions)), "typescript-formatter", - readFileFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-serve.js")), + SimpleResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-serve.js")), buildDir, npm); this.buildDir = requireNonNull(buildDir); From 3dcb1ec16593d7db7d1e5bcf091646e9d2224080 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 19:53:52 +0200 Subject: [PATCH 12/43] refactor and use npmProcess helper instance --- .../npm/NpmFormatterStepStateBase.java | 50 +++++------ .../com/diffplug/spotless/npm/NpmProcess.java | 89 +++++++++++++++++++ .../spotless/npm/SimpleResourceHelper.java | 12 +++ 3 files changed, 121 insertions(+), 30 deletions(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java 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 83e432def1..f58b0f6396 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -17,7 +17,10 @@ import static java.util.Objects.requireNonNull; -import java.io.*; +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.time.Duration; import java.util.Collections; import java.util.Iterator; import java.util.Map; @@ -27,7 +30,8 @@ import javax.annotation.Nullable; -import com.diffplug.spotless.*; +import com.diffplug.spotless.FileSignature; +import com.diffplug.spotless.FormatterFunc; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -66,18 +70,7 @@ private File prepareNodeServer(File buildDir) throws IOException { } private void runNpmInstall(File npmProjectDir) throws IOException { - Process npmInstall = new ProcessBuilder() - .inheritIO() - .directory(npmProjectDir) - .command(this.npmExecutable.getAbsolutePath(), "install", "--no-audit", "--no-package-lock") - .start(); - try { - if (npmInstall.waitFor() != 0) { - throw new IOException("Creating npm modules failed with exit code: " + npmInstall.exitValue()); - } - } catch (InterruptedException e) { - throw new IOException("Running npm install was interrupted.", e); - } + new NpmProcess(npmProjectDir, this.npmExecutable).install(); } protected ServerProcessInfo npmRunServer() throws ServerStartException { @@ -87,25 +80,22 @@ protected ServerProcessInfo npmRunServer() throws ServerStartException { final File serverPortFile = new File(this.nodeModulesDir, "server.port"); SimpleResourceHelper.deleteFileIfExists(serverPortFile); // start the http server in node - Process server = new ProcessBuilder() - .inheritIO() - .directory(this.nodeModulesDir) - .command(this.npmExecutable.getAbsolutePath(), "start") - .start(); - - // await the readiness of the http server - final long startedAt = System.currentTimeMillis(); - while (!serverPortFile.exists() || !serverPortFile.canRead()) { - // wait for at most 60 seconds - if it is not ready by then, something is terribly wrong - if ((System.currentTimeMillis() - startedAt) > (60 * 1000L)) { - // forcibly end the server process - try { + Process server = new NpmProcess(this.nodeModulesDir, this.npmExecutable).start(); + + // await the readiness of the http server - wait for at most 60 seconds + try { + SimpleResourceHelper.awaitReadableFile(serverPortFile, Duration.ofSeconds(60)); + } catch (TimeoutException timeoutException) { + // forcibly end the server process + try { + if (server.isAlive()) { server.destroyForcibly(); - } catch (Throwable t) { - // ignore + server.waitFor(); } - throw new TimeoutException("The server did not startup in the requested time frame of 60 seconds."); + } catch (Throwable t) { + // ignore } + throw timeoutException; } // read the server.port file for resulting port and remember the port for later formatting calls String serverPort = SimpleResourceHelper.readUtf8StringFromFile(serverPortFile).trim(); diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java new file mode 100644 index 0000000000..87bb59e047 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java @@ -0,0 +1,89 @@ +/* + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.npm; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +class NpmProcess { + + private final File workingDir; + + private final File npmExecutable; + + NpmProcess(File workingDir, File npmExecutable) { + this.workingDir = workingDir; + this.npmExecutable = npmExecutable; + } + + void install() { + npmAwait("install", "--no-audit", "--no-package-lock"); + } + + Process start() { + return npm("start"); + } + + private void npmAwait(String... args) { + final Process npmProcess = npm(args); + + try { + if (npmProcess.waitFor() != 0) { + throw new NpmProcessException("Running npm command '" + commandLine(args) + "' failed with exit code: " + npmProcess.exitValue()); + } + } catch (InterruptedException e) { + throw new NpmProcessException("Running npm command '" + commandLine(args) + "' was interrupted.", e); + } + } + + private Process npm(String... args) { + List processCommand = processCommand(args); + try { + return new ProcessBuilder() + .inheritIO() + .directory(this.workingDir) + .command(processCommand) + .start(); + } catch (IOException e) { + throw new NpmProcessException("Failed to launch npm command '" + commandLine(args) + "'.", e); + } + } + + private List processCommand(String... args) { + List command = new ArrayList<>(args.length + 1); + command.add(this.npmExecutable.getAbsolutePath()); + command.addAll(Arrays.asList(args)); + return command; + } + + private String commandLine(String... args) { + return "npm " + Arrays.stream(args).collect(Collectors.joining(" ")); + } + + static class NpmProcessException extends RuntimeException { + public NpmProcessException(String message) { + super(message); + } + + public NpmProcessException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleResourceHelper.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleResourceHelper.java index e2a1b1328e..b297e38a57 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleResourceHelper.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleResourceHelper.java @@ -21,6 +21,8 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.time.Duration; +import java.util.concurrent.TimeoutException; import com.diffplug.spotless.ThrowingEx; @@ -71,4 +73,14 @@ static void assertDirectoryExists(File directory) throws IOException { } } } + + static void awaitReadableFile(File file, Duration maxWaitTime) throws TimeoutException { + final long startedAt = System.currentTimeMillis(); + while (!file.exists() || !file.canRead()) { + // wait for at most maxWaitTime + if ((System.currentTimeMillis() - startedAt) > maxWaitTime.toMillis()) { + throw new TimeoutException("The file did not appear within " + maxWaitTime); + } + } + } } From 78a0ea806ca855cbad0496293f620285540faa07 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 20:25:15 +0200 Subject: [PATCH 13/43] reduce log noise from npm install call due to missing package.json files --- .../resources/com/diffplug/spotless/npm/prettier-package.json | 3 +++ .../resources/com/diffplug/spotless/npm/tsfmt-package.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json index acd47c82c2..89add57b91 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json @@ -1,6 +1,9 @@ { "name": "spotless-prettier-formatter-step", "version": "2.0.0", + "description": "Spotless formatter step for running prettier as a rest service.", + "repository": "https://github.com/diffplug/spotless", + "license": "Apache-2.0", "scripts": { "start": "node serve.js" }, diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json index 0bb5476d80..df3e247097 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json @@ -1,6 +1,9 @@ { "name": "spotless-tsfmt-formatter-step", "version": "2.0.0", + "description": "Spotless formatter step for running tsfmt as a rest service.", + "repository": "https://github.com/diffplug/spotless", + "license": "Apache-2.0", "scripts": { "start": "node serve.js" }, From bc3c86a259469a894dd4534e1f5bdb4c8466d376 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 20:25:29 +0200 Subject: [PATCH 14/43] only listen on localhost --- .../main/resources/com/diffplug/spotless/npm/prettier-serve.js | 2 +- lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js index 85d4a6ed1e..5ab794cc7e 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js @@ -6,7 +6,7 @@ const prettier = require("prettier"); const fs = require("fs"); -var listener = app.listen(() => { +var listener = app.listen(0, '127.0.0.1', () => { console.log("Server running on port " + listener.address().port); fs.writeFile('server.port.tmp', '' + listener.address().port, function (err) { if (err) { diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js index acdcd5ea76..cde1e9ee37 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js @@ -6,7 +6,7 @@ const tsfmt = require("typescript-formatter"); const fs = require("fs"); -var listener = app.listen(() => { +var listener = app.listen(0, '127.0.0.1', () => { console.log("Server running on port " + listener.address().port); fs.writeFile('server.port.tmp', '' + listener.address().port, function (err) { if (err) { From 57f97b9906d7efce3fcbb20b088db9c70b46b1da Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 20:25:39 +0200 Subject: [PATCH 15/43] cleanup logging --- .../main/resources/com/diffplug/spotless/npm/tsfmt-serve.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js index cde1e9ee37..a972ae7b6c 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js @@ -23,11 +23,7 @@ var listener = app.listen(0, '127.0.0.1', () => { app.post("/tsfmt/format", (req, res) => { var format_data = req.body; - - console.log("formatData", format_data) - tsfmt.processString("spotless-format-string.ts", format_data.file_content, format_data.config_options).then((resultMap) => { - console.log("resultMap", resultMap); /* export interface ResultMap { [fileName: string]: Result; @@ -49,6 +45,5 @@ app.post("/tsfmt/format", (req, res) => { } res.set('Content-Type', 'text/plain') res.send(resultMap.dest); - }); }); From d7f356498e690f5ea95696a547edb6f8ae754562 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 20:39:45 +0200 Subject: [PATCH 16/43] enable spotless for js formatting --- gradle/spotless.gradle | 4 ++++ gradle/spotless.javascript.prettierrc.yml | 3 +++ 2 files changed, 7 insertions(+) create mode 100644 gradle/spotless.javascript.prettierrc.yml diff --git a/gradle/spotless.gradle b/gradle/spotless.gradle index 9fe948ba1b..fbf90f1bbc 100644 --- a/gradle/spotless.gradle +++ b/gradle/spotless.gradle @@ -25,6 +25,10 @@ spotless { removeUnusedImports() } } + format 'javascript', { + target 'src/main/resources/**/*.js' + prettier().configFile(rootProject.file('gradle/spotless.javascript.prettierrc.yml')) + } groovyGradle { target '*.gradle' greclipse().configFile rootProject.files('gradle/spotless.eclipseformat.xml', 'gradle/spotless.groovyformat.prefs') diff --git a/gradle/spotless.javascript.prettierrc.yml b/gradle/spotless.javascript.prettierrc.yml new file mode 100644 index 0000000000..c6bfdaefc3 --- /dev/null +++ b/gradle/spotless.javascript.prettierrc.yml @@ -0,0 +1,3 @@ +parser: flow +useTabs: true +printWidth: 999 From ee2dd1de2c132ab34dfd4de82d70251ab56088b6 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 20:39:56 +0200 Subject: [PATCH 17/43] format js files using spotless --- .../diffplug/spotless/npm/prettier-serve.js | 127 +++++++++--------- .../com/diffplug/spotless/npm/tsfmt-serve.js | 50 +++---- 2 files changed, 88 insertions(+), 89 deletions(-) diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js index 5ab794cc7e..6fc2e1ef15 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js @@ -1,85 +1,84 @@ const express = require("express"); const app = express(); -app.use(express.json({ limit: '50mb' })); +app.use(express.json({ limit: "50mb" })); const prettier = require("prettier"); const fs = require("fs"); -var listener = app.listen(0, '127.0.0.1', () => { - console.log("Server running on port " + listener.address().port); - fs.writeFile('server.port.tmp', '' + listener.address().port, function (err) { - if (err) { - return console.log(err); - } else { - fs.rename('server.port.tmp', 'server.port', function (err) { - if (err) { - return console.log(err); - } - }); // try to be as atomic as possible - } - }); +var listener = app.listen(0, "127.0.0.1", () => { + console.log("Server running on port " + listener.address().port); + fs.writeFile("server.port.tmp", "" + listener.address().port, function(err) { + if (err) { + return console.log(err); + } else { + fs.rename("server.port.tmp", "server.port", function(err) { + if (err) { + return console.log(err); + } + }); // try to be as atomic as possible + } + }); }); app.post("/prettier/config-options", (req, res) => { - var config_data = req.body; - var prettier_config_path = config_data.prettier_config_path; - var prettier_config_options = config_data.prettier_config_options || {}; + var config_data = req.body; + var prettier_config_path = config_data.prettier_config_path; + var prettier_config_options = config_data.prettier_config_options || {}; - if (prettier_config_path) { - prettier.resolveConfig(undefined, {config: prettier_config_path}) - .then(options => { - var mergedConfigOptions = mergeConfigOptions(options, prettier_config_options); - res.json(mergedConfigOptions); - }) - .catch(reason => res.status(501).send('Exception while resolving config_file_path: ' + reason)); - return; - } - res.json(prettier_config_options); + if (prettier_config_path) { + prettier + .resolveConfig(undefined, { config: prettier_config_path }) + .then(options => { + var mergedConfigOptions = mergeConfigOptions(options, prettier_config_options); + res.json(mergedConfigOptions); + }) + .catch(reason => res.status(501).send("Exception while resolving config_file_path: " + reason)); + return; + } + res.json(prettier_config_options); }); app.post("/prettier/format", (req, res) => { - var format_data = req.body; + var format_data = req.body; - var formatted_file_content = prettier.format(format_data.file_content, format_data.config_options); - res.set('Content-Type', 'text/plain') - res.send(formatted_file_content); + var formatted_file_content = prettier.format(format_data.file_content, format_data.config_options); + res.set("Content-Type", "text/plain"); + res.send(formatted_file_content); }); -var mergeConfigOptions = function (resolved_config_options, config_options) { - if (resolved_config_options !== undefined && config_options !== undefined) { - return extend(resolved_config_options, config_options); - } - if (resolved_config_options === undefined) { - return config_options; - } - if (config_options === undefined) { - return resolved_config_options; - } -} +var mergeConfigOptions = function(resolved_config_options, config_options) { + if (resolved_config_options !== undefined && config_options !== undefined) { + return extend(resolved_config_options, config_options); + } + if (resolved_config_options === undefined) { + return config_options; + } + if (config_options === undefined) { + return resolved_config_options; + } +}; -var extend = function () { +var extend = function() { + // Variables + var extended = {}; + var i = 0; + var length = arguments.length; - // Variables - var extended = {}; - var i = 0; - var length = arguments.length; + // Merge the object into the extended object + var merge = function(obj) { + for (var prop in obj) { + if (Object.prototype.hasOwnProperty.call(obj, prop)) { + extended[prop] = obj[prop]; + } + } + }; - // Merge the object into the extended object - var merge = function (obj) { - for (var prop in obj) { - if (Object.prototype.hasOwnProperty.call(obj, prop)) { - extended[prop] = obj[prop]; - } - } - }; + // Loop through each object and conduct a merge + for (; i < length; i++) { + var obj = arguments[i]; + merge(obj); + } - // Loop through each object and conduct a merge - for (; i < length; i++) { - var obj = arguments[i]; - merge(obj); - } - - return extended; - -}; \ No newline at end of file + return extended; +}; diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js index a972ae7b6c..e682a1d0d4 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js @@ -1,30 +1,30 @@ const express = require("express"); const app = express(); -app.use(express.json({ limit: '50mb' })); +app.use(express.json({ limit: "50mb" })); const tsfmt = require("typescript-formatter"); const fs = require("fs"); -var listener = app.listen(0, '127.0.0.1', () => { - console.log("Server running on port " + listener.address().port); - fs.writeFile('server.port.tmp', '' + listener.address().port, function (err) { - if (err) { - return console.log(err); - } else { - fs.rename('server.port.tmp', 'server.port', function (err) { - if (err) { - return console.log(err); - } - }); // try to be as atomic as possible - } - }); +var listener = app.listen(0, "127.0.0.1", () => { + console.log("Server running on port " + listener.address().port); + fs.writeFile("server.port.tmp", "" + listener.address().port, function(err) { + if (err) { + return console.log(err); + } else { + fs.rename("server.port.tmp", "server.port", function(err) { + if (err) { + return console.log(err); + } + }); // try to be as atomic as possible + } + }); }); app.post("/tsfmt/format", (req, res) => { - var format_data = req.body; - tsfmt.processString("spotless-format-string.ts", format_data.file_content, format_data.config_options).then((resultMap) => { - /* + var format_data = req.body; + tsfmt.processString("spotless-format-string.ts", format_data.file_content, format_data.config_options).then(resultMap => { + /* export interface ResultMap { [fileName: string]: Result; } @@ -38,12 +38,12 @@ app.post("/tsfmt/format", (req, res) => { dest: string; } */ - // result contains 'message' (String), 'error' (boolean), 'dest' (String) => formatted - if (resultMap.error !== undefined && resultMap.error) { - res.status(400).send(resultmap.message); - return; - } - res.set('Content-Type', 'text/plain') - res.send(resultMap.dest); - }); + // result contains 'message' (String), 'error' (boolean), 'dest' (String) => formatted + if (resultMap.error !== undefined && resultMap.error) { + res.status(400).send(resultmap.message); + return; + } + res.set("Content-Type", "text/plain"); + res.send(resultMap.dest); + }); }); From fbb3961fb1b13b02a3b35666324de38d66bd0fca Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 21:04:44 +0200 Subject: [PATCH 18/43] refactoring: extract inner classes and renamings and improve http resource handling --- .../diffplug/spotless/npm/JsonEscaper.java | 110 ++++++++++++++++++ .../diffplug/spotless/npm/JsonRawValue.java | 37 ++++++ .../npm/NpmFormatterStepStateBase.java | 14 +-- ...urceHelper.java => NpmResourceHelper.java} | 40 ++++--- .../spotless/npm/PrettierFormatterStep.java | 4 +- .../spotless/npm/PrettierRestService.java | 6 +- .../spotless/npm/SimpleJsonWriter.java | 95 +-------------- .../spotless/npm/SimpleRestClient.java | 29 ++--- .../spotless/npm/TsFmtFormatterStep.java | 4 +- .../spotless/npm/TsFmtRestService.java | 2 +- 10 files changed, 203 insertions(+), 138 deletions(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/npm/JsonEscaper.java create mode 100644 lib/src/main/java/com/diffplug/spotless/npm/JsonRawValue.java rename lib/src/main/java/com/diffplug/spotless/npm/{SimpleResourceHelper.java => NpmResourceHelper.java} (77%) diff --git a/lib/src/main/java/com/diffplug/spotless/npm/JsonEscaper.java b/lib/src/main/java/com/diffplug/spotless/npm/JsonEscaper.java new file mode 100644 index 0000000000..71e33b65b6 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/JsonEscaper.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.npm; + +import static java.util.Objects.requireNonNull; + +/** + * Simple implementation on how to escape values when printing json. + * Implementation is partly based on https://github.com/stleary/JSON-java + */ +final class JsonEscaper { + private JsonEscaper() { + // no instance + } + + public static String jsonEscape(Object val) { + requireNonNull(val); + if (val instanceof JsonRawValue) { + return jsonEscape((JsonRawValue) val); + } + if (val instanceof String) { + return jsonEscape((String) val); + } + return val.toString(); + } + + private static String jsonEscape(JsonRawValue jsonRawValue) { + return jsonRawValue.getRawJson(); + } + + private static String jsonEscape(String unescaped) { + /** + * the following characters are reserved in JSON and must be properly escaped to be used in strings: + * + * Backspace is replaced with \b + * Form feed is replaced with \f + * Newline is replaced with \n + * Carriage return is replaced with \r + * Tab is replaced with \t + * Double quote is replaced with \" + * Backslash is replaced with \\ + * + * additionally we handle xhtml '' string + * and non-ascii chars + */ + StringBuilder escaped = new StringBuilder(); + escaped.append('"'); + char b; + char c = 0; + for (int i = 0; i < unescaped.length(); i++) { + b = c; + c = unescaped.charAt(i); + switch (c) { + case '\"': + escaped.append('\\').append('"'); + break; + case '\n': + escaped.append('\\').append('n'); + break; + case '\r': + escaped.append('\\').append('r'); + break; + case '\t': + escaped.append('\\').append('t'); + break; + case '\b': + escaped.append('\\').append('b'); + break; + case '\f': + escaped.append('\\').append('f'); + break; + case '\\': + escaped.append('\\').append('\\'); + break; + case '/': + if (b == '<') { + escaped.append('\\'); + } + escaped.append(c); + break; + default: + if (c < ' ' || (c >= '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100')) { + escaped.append('\\').append('u'); + String hexString = Integer.toHexString(c); + escaped.append("0000", 0, 4 - hexString.length()); + escaped.append(hexString); + } else { + escaped.append(c); + } + } + } + escaped.append('"'); + return escaped.toString(); + } + +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/JsonRawValue.java b/lib/src/main/java/com/diffplug/spotless/npm/JsonRawValue.java new file mode 100644 index 0000000000..c2ba830f12 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/JsonRawValue.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.npm; + +import static java.util.Objects.requireNonNull; + +/** + * Wrapper class to signal the contained string must not be escaped when printing to json. + */ +class JsonRawValue { + private final String rawJson; + + private JsonRawValue(String rawJson) { + this.rawJson = requireNonNull(rawJson); + } + + static JsonRawValue wrap(String rawJson) { + return new JsonRawValue(rawJson); + } + + public String getRawJson() { + return rawJson; + } +} 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 f58b0f6396..0f288225b0 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -62,9 +62,9 @@ protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, File b private File prepareNodeServer(File buildDir) throws IOException { File targetDir = new File(buildDir, "spotless-node-modules-" + stepName); - SimpleResourceHelper.assertDirectoryExists(targetDir); - SimpleResourceHelper.writeUtf8StringToFile(targetDir, "package.json", this.npmConfig.getPackageJsonContent()); - SimpleResourceHelper.writeUtf8StringToFile(targetDir, "serve.js", this.npmConfig.getServeScriptContent()); + NpmResourceHelper.assertDirectoryExists(targetDir); + NpmResourceHelper.writeUtf8StringToFile(targetDir, "package.json", this.npmConfig.getPackageJsonContent()); + NpmResourceHelper.writeUtf8StringToFile(targetDir, "serve.js", this.npmConfig.getServeScriptContent()); runNpmInstall(targetDir); return targetDir; } @@ -78,13 +78,13 @@ protected ServerProcessInfo npmRunServer() throws ServerStartException { // The npm process will output the randomly selected port of the http server process to 'server.port' file // so in order to be safe, remove such a file if it exists before starting. final File serverPortFile = new File(this.nodeModulesDir, "server.port"); - SimpleResourceHelper.deleteFileIfExists(serverPortFile); + NpmResourceHelper.deleteFileIfExists(serverPortFile); // start the http server in node Process server = new NpmProcess(this.nodeModulesDir, this.npmExecutable).start(); // await the readiness of the http server - wait for at most 60 seconds try { - SimpleResourceHelper.awaitReadableFile(serverPortFile, Duration.ofSeconds(60)); + NpmResourceHelper.awaitReadableFile(serverPortFile, Duration.ofSeconds(60)); } catch (TimeoutException timeoutException) { // forcibly end the server process try { @@ -98,7 +98,7 @@ protected ServerProcessInfo npmRunServer() throws ServerStartException { throw timeoutException; } // read the server.port file for resulting port and remember the port for later formatting calls - String serverPort = SimpleResourceHelper.readUtf8StringFromFile(serverPortFile).trim(); + String serverPort = NpmResourceHelper.readUtf8StringFromFile(serverPortFile).trim(); return new ServerProcessInfo(server, serverPort, serverPortFile); } catch (IOException | TimeoutException e) { throw new ServerStartException(e); @@ -155,7 +155,7 @@ public String getBaseUrl() { @Override public void close() throws Exception { - SimpleResourceHelper.deleteFileIfExists(serverPortFile); + NpmResourceHelper.deleteFileIfExists(serverPortFile); if (this.server.isAlive()) { this.server.destroy(); } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleResourceHelper.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java similarity index 77% rename from lib/src/main/java/com/diffplug/spotless/npm/SimpleResourceHelper.java rename to lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java index b297e38a57..18e23ad897 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleResourceHelper.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java @@ -15,10 +15,7 @@ */ package com.diffplug.spotless.npm; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.time.Duration; @@ -26,14 +23,19 @@ import com.diffplug.spotless.ThrowingEx; -final class SimpleResourceHelper { - private SimpleResourceHelper() { +final class NpmResourceHelper { + private NpmResourceHelper() { // no instance required } - static void writeUtf8StringToFile(File targetDir, String fileName, String packageJsonContent) throws IOException { + static void writeUtf8StringToFile(File targetDir, String fileName, String stringToWrite) throws IOException { File packageJsonFile = new File(targetDir, fileName); - Files.write(packageJsonFile.toPath(), packageJsonContent.getBytes(StandardCharsets.UTF_8)); + Files.write(packageJsonFile.toPath(), stringToWrite.getBytes(StandardCharsets.UTF_8)); + } + + static void writeUtf8StringToOutputStream(String stringToWrite, OutputStream outputStream) throws IOException { + final byte[] bytes = stringToWrite.getBytes(StandardCharsets.UTF_8); + outputStream.write(bytes); } static void deleteFileIfExists(File file) throws IOException { @@ -45,14 +47,8 @@ static void deleteFileIfExists(File file) throws IOException { } static String readUtf8StringFromClasspath(Class clazz, String resourceName) { - ByteArrayOutputStream output = new ByteArrayOutputStream(); try (InputStream input = clazz.getResourceAsStream(resourceName)) { - byte[] buffer = new byte[1024]; - int numRead; - while ((numRead = input.read(buffer)) != -1) { - output.write(buffer, 0, numRead); - } - return output.toString(StandardCharsets.UTF_8.name()); + return readUtf8StringFromInputStream(input); } catch (IOException e) { throw ThrowingEx.asRuntime(e); } @@ -66,6 +62,20 @@ static String readUtf8StringFromFile(File file) { } } + static String readUtf8StringFromInputStream(InputStream input) { + try { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int numRead; + while ((numRead = input.read(buffer)) != -1) { + output.write(buffer, 0, numRead); + } + return output.toString(StandardCharsets.UTF_8.name()); + } catch (IOException e) { + throw ThrowingEx.asRuntime(e); + } + } + static void assertDirectoryExists(File directory) throws IOException { if (!directory.exists()) { if (!directory.mkdirs()) { 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 38799a0db1..5025289be5 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -67,10 +67,10 @@ public static class State extends NpmFormatterStepStateBase implements Serializa super(stepName, new NpmConfig( replaceDevDependencies( - SimpleResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-package.json"), + NpmResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-package.json"), new TreeMap<>(devDependencies)), "prettier", - SimpleResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-serve.js")), + NpmResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-serve.js")), buildDir, npm); this.prettierConfig = requireNonNull(prettierConfig); diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java index d28a55dd27..5ec4980c01 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java @@ -19,8 +19,6 @@ import java.util.LinkedHashMap; import java.util.Map; -import com.diffplug.spotless.npm.SimpleJsonWriter.RawJsonValue; - public class PrettierRestService { private final SimpleRestClient restClient; @@ -35,7 +33,7 @@ public String resolveConfig(File prettierConfigPath, Map prettie jsonProperties.put("prettier_config_path", prettierConfigPath.getAbsolutePath()); } if (prettierConfigOptions != null) { - jsonProperties.put("prettier_config_options", SimpleJsonWriter.of(prettierConfigOptions).toRawJsonValue()); + jsonProperties.put("prettier_config_options", SimpleJsonWriter.of(prettierConfigOptions).toJsonRawValue()); } return restClient.postJson("/prettier/config-options", jsonProperties); @@ -45,7 +43,7 @@ public String format(String fileContent, String configOptionsJsonString) { Map jsonProperties = new LinkedHashMap<>(); jsonProperties.put("file_content", fileContent); if (configOptionsJsonString != null) { - jsonProperties.put("config_options", RawJsonValue.wrap(configOptionsJsonString)); + jsonProperties.put("config_options", JsonRawValue.wrap(configOptionsJsonString)); } return restClient.postJson("/prettier/format", jsonProperties); diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java index 31c06ad03f..ed21c36641 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java @@ -15,7 +15,7 @@ */ package com.diffplug.spotless.npm; -import static java.util.Objects.requireNonNull; +import static com.diffplug.spotless.npm.JsonEscaper.jsonEscape; import java.io.File; import java.io.IOException; @@ -53,8 +53,8 @@ SimpleJsonWriter put(String name, Object value) { private void verifyValues(Map values) { if (values.values() .stream() - .anyMatch(val -> !(val instanceof String || val instanceof RawJsonValue || val instanceof Number || val instanceof Boolean))) { - throw new IllegalArgumentException("Only values of type 'String', 'RawJsonValue', 'Number' and 'Boolean' are supported. You provided: " + values.values()); + .anyMatch(val -> !(val instanceof String || val instanceof JsonRawValue || val instanceof Number || val instanceof Boolean))) { + throw new IllegalArgumentException("Only values of type 'String', 'JsonRawValue', 'Number' and 'Boolean' are supported. You provided: " + values.values()); } } @@ -66,78 +66,8 @@ String toJsonString() { return "{\n" + valueString + "\n}"; } - RawJsonValue toRawJsonValue() { - return RawJsonValue.wrap(toJsonString()); - } - - private String jsonEscape(Object val) { - requireNonNull(val); - if (val instanceof RawJsonValue) { - return ((RawJsonValue) val).getRawJson(); - } - if (val instanceof String) { - /** - * the following characters are reserved in JSON and must be properly escaped to be used in strings: - * - * Backspace is replaced with \b - * Form feed is replaced with \f - * Newline is replaced with \n - * Carriage return is replaced with \r - * Tab is replaced with \t - * Double quote is replaced with \" - * Backslash is replaced with \\ - */ - StringBuilder escaped = new StringBuilder(); - escaped.append('"'); - char b; - char c = 0; - for (int i = 0; i < ((String) val).length(); i++) { - b = c; - c = ((String) val).charAt(i); - switch (c) { - case '\"': - escaped.append('\\').append('"'); - break; - case '\n': - escaped.append('\\').append('n'); - break; - case '\r': - escaped.append('\\').append('r'); - break; - case '\t': - escaped.append('\\').append('t'); - break; - case '\b': - escaped.append('\\').append('b'); - break; - case '\f': - escaped.append('\\').append('f'); - break; - case '\\': - escaped.append('\\').append('\\'); - break; - case '/': - if (b == '<') { - escaped.append('\\'); - } - escaped.append(c); - break; - default: - if (c < ' ' || (c >= '\u0080' && c < '\u00a0') - || (c >= '\u2000' && c < '\u2100')) { - escaped.append('\\').append('u'); - String hexString = Integer.toHexString(c); - escaped.append("0000", 0, 4 - hexString.length()); - escaped.append(hexString); - } else { - escaped.append(c); - } - } - } - escaped.append('"'); - return escaped.toString(); - } - return val.toString(); + JsonRawValue toJsonRawValue() { + return JsonRawValue.wrap(toJsonString()); } void toJsonFile(File file) { @@ -158,19 +88,4 @@ public String toString() { return this.toJsonString(); } - static class RawJsonValue { - private final String rawJson; - - private RawJsonValue(String rawJson) { - this.rawJson = requireNonNull(rawJson); - } - - static RawJsonValue wrap(String rawJson) { - return new RawJsonValue(rawJson); - } - - public String getRawJson() { - return rawJson; - } - } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java index d8358500a5..e382c2e3a4 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java @@ -17,11 +17,11 @@ import static java.util.Objects.requireNonNull; -import java.io.*; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.util.Map; import javax.annotation.Nonnull; @@ -48,13 +48,15 @@ String postJson(String endpoint, String rawJson) throws SimpleRestException { try { URL url = new URL(this.baseUrl + endpoint); HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setConnectTimeout(60 * 1000); // one minute + con.setReadTimeout(2 * 60 * 1000); // two minutes - who knows how large those files can actually get con.setRequestMethod("POST"); con.setRequestProperty("Content-Type", "application/json"); con.setDoOutput(true); - DataOutputStream out = new DataOutputStream(con.getOutputStream()); - out.writeBytes(rawJson); - out.flush(); - out.close(); + try (OutputStream out = con.getOutputStream()) { + NpmResourceHelper.writeUtf8StringToOutputStream(rawJson, out); + out.flush(); + } int status = con.getResponseCode(); @@ -62,23 +64,16 @@ String postJson(String endpoint, String rawJson) throws SimpleRestException { throw new SimpleRestResponseException(status, con.getResponseMessage(), "Unexpected response status code."); } - String response = readResponse(con, StandardCharsets.UTF_8); + String response = readResponse(con); return response; } catch (IOException e) { throw new SimpleRestIOException(e); } } - private String readResponse(HttpURLConnection con, Charset charset) throws IOException { - String encoding = con.getContentEncoding(); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int numRead; + private String readResponse(HttpURLConnection con) throws IOException { try (BufferedInputStream input = new BufferedInputStream(con.getInputStream())) { - while ((numRead = input.read(buffer)) != -1) { - output.write(buffer, 0, numRead); - } - return output.toString(charset.name()); + return NpmResourceHelper.readUtf8StringFromInputStream(input); } } 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 f8114e8934..45175a7375 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java @@ -77,9 +77,9 @@ public State(String stepName, File buildDir, @Nullable File npm, @Nullable Typed public State(String stepName, Map versions, File buildDir, @Nullable File npm, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map inlineTsFmtSettings) throws IOException { super(stepName, new NpmConfig( - replaceDevDependencies(SimpleResourceHelper.readUtf8StringFromClasspath(TsFmtFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-package.json"), new TreeMap<>(versions)), + replaceDevDependencies(NpmResourceHelper.readUtf8StringFromClasspath(TsFmtFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-package.json"), new TreeMap<>(versions)), "typescript-formatter", - SimpleResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-serve.js")), + NpmResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-serve.js")), buildDir, npm); this.buildDir = requireNonNull(buildDir); diff --git a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java index 0022b33556..2c4092e2cc 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java @@ -30,7 +30,7 @@ public String format(String fileContent, Map configOptions) { Map jsonProperties = new LinkedHashMap<>(); jsonProperties.put("file_content", fileContent); if (configOptions != null && !configOptions.isEmpty()) { - jsonProperties.put("config_options", SimpleJsonWriter.of(configOptions).toRawJsonValue()); + jsonProperties.put("config_options", SimpleJsonWriter.of(configOptions).toJsonRawValue()); } return restClient.postJson("/tsfmt/format", jsonProperties); From eafb850888c93e36d6c7cb2abf4f2cef02ac7706 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 21:27:02 +0200 Subject: [PATCH 19/43] adapt changelog --- CHANGES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index a9d114a073..4ee55c4d60 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,11 @@ 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] +### Changed +* Nodejs-based formatters `prettier` and `tsfmt` now use native node instead of the J2V8 approach. (([#606](https://github.com/diffplug/spotless/pull/606))) + * This removes the dependency to the no-longer-maintained Linux/Windows/macOs variants of J2V8. + * This enables spotless to use the latest `prettier` versions (instead of being stuck at prettier version <= `1.19.0`) + ## [1.34.0] - 2020-06-05 ### Added From d47a94910bd332d368f91b686669fcbb9726bfcf Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 22:46:03 +0200 Subject: [PATCH 20/43] adapt to newer copyright header --- lib/src/main/java/com/diffplug/spotless/npm/JsonEscaper.java | 2 +- lib/src/main/java/com/diffplug/spotless/npm/JsonRawValue.java | 2 +- lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java | 2 +- .../java/com/diffplug/spotless/npm/NpmExecutableResolver.java | 2 +- .../com/diffplug/spotless/npm/NpmFormatterStepStateBase.java | 2 +- lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java | 2 +- .../main/java/com/diffplug/spotless/npm/NpmResourceHelper.java | 2 +- .../java/com/diffplug/spotless/npm/PrettierFormatterStep.java | 2 +- .../java/com/diffplug/spotless/npm/PrettierRestService.java | 2 +- .../main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java | 2 +- .../main/java/com/diffplug/spotless/npm/SimpleRestClient.java | 2 +- .../main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java | 2 +- .../main/java/com/diffplug/spotless/npm/TsFmtRestService.java | 2 +- .../com/diffplug/spotless/generic/LicenseHeaderStepTest.java | 2 +- .../com/diffplug/spotless/npm/PrettierFormatterStepTest.java | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/npm/JsonEscaper.java b/lib/src/main/java/com/diffplug/spotless/npm/JsonEscaper.java index 71e33b65b6..163818d0e7 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/JsonEscaper.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/JsonEscaper.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/lib/src/main/java/com/diffplug/spotless/npm/JsonRawValue.java b/lib/src/main/java/com/diffplug/spotless/npm/JsonRawValue.java index c2ba830f12..332dfb7c17 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/JsonRawValue.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/JsonRawValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java index 789da2b34d..7fe3daf0b7 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/lib/src/main/java/com/diffplug/spotless/npm/NpmExecutableResolver.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmExecutableResolver.java index f36981b39d..5be273621e 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmExecutableResolver.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmExecutableResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java index 0f288225b0..ab5bba08dd 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java index 87bb59e047..f2444b3313 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java index 18e23ad897..3e5bcd68b4 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java index 5025289be5..2ee7cedfe1 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java index 5ec4980c01..70aaf23d3b 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java index ed21c36641..846f7f1cf3 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java index e382c2e3a4..06eb3d0e1d 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java index 45175a7375..847c1fa64c 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java index 2c4092e2cc..a847fa074d 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java b/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java index 65c6e61989..2b0d2a4937 100644 --- a/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 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/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java b/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java index 613bd92955..67f5cfdfc9 100644 --- a/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 674044bb7d838a214342a9ef93742d0c6c6ad208 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Sun, 7 Jun 2020 22:48:09 +0200 Subject: [PATCH 21/43] try to fix circle-ci (use node for running internal spotlessJavascript) --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3b02e9d88d..673220e792 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -50,6 +50,8 @@ jobs: # gradlew spotlessCheck assemble testClasses assemble_testClasses: <<: *env_gradle_large + docker: + - image: cimg/openjdk:8.0-node # use node due to spotlessJavascript steps: - checkout - *restore_cache_wrapper From c70bc8590d2f1e20f8eeaf2c6f7a5f1f65c74b95 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Sun, 7 Jun 2020 17:42:49 -0700 Subject: [PATCH 22/43] Remove javascript from our dogfooding. - I'm a big fan of prettier, and use it a lot myself - But I don't want npm to be a must-have for Spotless devs - That is why we have a separate CircleCI job `test_npm_8`, which is the only one that requires npm --- .circleci/config.yml | 2 -- gradle/spotless.gradle | 4 ---- gradle/spotless.javascript.prettierrc.yml | 3 --- 3 files changed, 9 deletions(-) delete mode 100644 gradle/spotless.javascript.prettierrc.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index 673220e792..3b02e9d88d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -50,8 +50,6 @@ jobs: # gradlew spotlessCheck assemble testClasses assemble_testClasses: <<: *env_gradle_large - docker: - - image: cimg/openjdk:8.0-node # use node due to spotlessJavascript steps: - checkout - *restore_cache_wrapper diff --git a/gradle/spotless.gradle b/gradle/spotless.gradle index fbf90f1bbc..9fe948ba1b 100644 --- a/gradle/spotless.gradle +++ b/gradle/spotless.gradle @@ -25,10 +25,6 @@ spotless { removeUnusedImports() } } - format 'javascript', { - target 'src/main/resources/**/*.js' - prettier().configFile(rootProject.file('gradle/spotless.javascript.prettierrc.yml')) - } groovyGradle { target '*.gradle' greclipse().configFile rootProject.files('gradle/spotless.eclipseformat.xml', 'gradle/spotless.groovyformat.prefs') diff --git a/gradle/spotless.javascript.prettierrc.yml b/gradle/spotless.javascript.prettierrc.yml deleted file mode 100644 index c6bfdaefc3..0000000000 --- a/gradle/spotless.javascript.prettierrc.yml +++ /dev/null @@ -1,3 +0,0 @@ -parser: flow -useTabs: true -printWidth: 999 From 12680c53faa5610c1f173e1a359c1309ef643ffa Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Sun, 7 Jun 2020 17:55:13 -0700 Subject: [PATCH 23/43] Fix spotbugs warning. --- .../com/diffplug/spotless/npm/NpmFormatterStepStateBase.java | 1 + 1 file changed, 1 insertion(+) 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 ab5bba08dd..4f9d64de7f 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -45,6 +45,7 @@ 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; private final NpmConfig npmConfig; From 92197e12651d3660e1af72e1486d6dfe9b15c7de Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Sun, 7 Jun 2020 17:57:30 -0700 Subject: [PATCH 24/43] Windows cache seems to be broken, we'll just let it be slow. --- .circleci/config.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3b02e9d88d..9abb5c24c2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -126,21 +126,9 @@ jobs: shell: cmd.exe steps: - checkout - # install openjdk8 - - restore_cache: - key: choco2-ojdkbuild8 - run: name: install command: choco install ojdkbuild8 - - save_cache: - key: choco2-ojdkbuild8 - paths: - - ~\AppData\Local\Temp\chocolatey\ojdkbuild8 - # do the test - - restore_cache: - keys: - - gradle-deps-win2-{{ checksum "build.gradle" }}-{{ checksum "gradle.properties" }} - - gradle-deps-win2- - run: name: gradlew check command: gradlew check --build-cache @@ -152,13 +140,6 @@ jobs: path: plugin-gradle/build/test-results/test - store_test_results: path: plugin-maven/build/test-results/test - - save_cache: - key: gradle-deps-win2-{{ checksum "build.gradle" }}-{{ checksum "gradle.properties" }} - paths: - - ~/.gradle/caches - - ~/.gradle/wrapper - - ~/.m2 - - ~/project/plugin-maven/build/localMavenRepository changelog_print: << : *env_gradle steps: From c0879e11fb81861f9a702a35e1a9111eddb048d8 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Mon, 8 Jun 2020 21:02:46 +0200 Subject: [PATCH 25/43] fixing ci build somehow the mavenRunner got stuck when there was no stdout/stderr output. Fixing implementation as to detect when that is the case and to simplify things actually read stdout/stderr within the actual main thread, not seperately spawned threads. --- .../diffplug/spotless/maven/MavenRunner.java | 54 ++++++------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenRunner.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenRunner.java index e7fbb88a14..9eb0e39623 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenRunner.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenRunner.java @@ -17,17 +17,12 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; +import java.io.*; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.Objects; -import com.diffplug.common.base.Throwables; -import com.diffplug.common.io.ByteStreams; import com.diffplug.spotless.LineEnding; /** @@ -69,13 +64,22 @@ private Result run() throws IOException, InterruptedException { ProcessBuilder builder = new ProcessBuilder(cmds); builder.directory(projectDir); Process process = builder.start(); - // slurp and return the stdout, stderr, and exitValue - Slurper output = new Slurper(process.getInputStream()); - Slurper error = new Slurper(process.getErrorStream()); int exitValue = process.waitFor(); - output.join(); - error.join(); - return new Result(exitValue, output.result(), error.result()); + try (InputStream processOutputStream = process.getInputStream(); InputStream processErrorStream = process.getErrorStream()) { + String output = readFully(processOutputStream); + String error = readFully(processErrorStream); + return new Result(exitValue, output, error); + } + } + + private String readFully(InputStream input) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int numRead; + while ((numRead = input.read(buffer)) != -1) { + output.write(buffer, 0, numRead); + } + return output.toString(StandardCharsets.UTF_8.name()); } /** Runs the command and asserts that exit code is 0. */ @@ -135,28 +139,4 @@ private static List getPlatformCmds(String cmd) { } } - private static class Slurper extends Thread { - private final InputStream input; - private volatile String result; - - Slurper(InputStream input) { - this.input = Objects.requireNonNull(input); - start(); - } - - @Override - public void run() { - try { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - ByteStreams.copy(input, output); - result = output.toString(Charset.defaultCharset().name()); - } catch (Exception e) { - result = Throwables.getStackTraceAsString(e); - } - } - - public String result() { - return result; - } - } } From 0d6bd65607484777204eec06180c9cbc6547831a Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Tue, 9 Jun 2020 14:46:46 +0200 Subject: [PATCH 26/43] fixing ci build the problem appears to be that on unix, the node-serve call is spawning sub-processes. Subprocesses, however, are obviously not correctly terminated by the java ProcessBuilder-API. The current solution is to actively call the rest service and request a shutdown of the process from within the api and only fall back to ProcessBuilder api if that does not work. --- .../npm/NpmFormatterStepStateBase.java | 20 ++++++++++++++++--- .../spotless/npm/PrettierFormatterStep.java | 19 ++++++++++++++---- .../spotless/npm/PrettierRestService.java | 4 ++++ .../spotless/npm/SimpleRestClient.java | 12 ++++++++--- .../spotless/npm/TsFmtFormatterStep.java | 19 +++++++++++++++--- .../spotless/npm/TsFmtRestService.java | 4 ++++ .../spotless/npm/prettier-package.json | 3 ++- .../diffplug/spotless/npm/prettier-serve.js | 11 +++++++++- .../diffplug/spotless/npm/tsfmt-package.json | 3 ++- .../com/diffplug/spotless/npm/tsfmt-serve.js | 10 ++++++++++ 10 files changed, 89 insertions(+), 16 deletions(-) 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 4f9d64de7f..ec8a4a5c23 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -26,7 +26,9 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; import javax.annotation.Nullable; @@ -37,6 +39,8 @@ abstract class NpmFormatterStepStateBase implements Serializable { + private static final Logger logger = Logger.getLogger(NpmFormatterStepStateBase.class.getName()); + private static final long serialVersionUID = 1460749955865959948L; @SuppressWarnings("unused") @@ -156,9 +160,19 @@ public String getBaseUrl() { @Override public void close() throws Exception { - NpmResourceHelper.deleteFileIfExists(serverPortFile); - if (this.server.isAlive()) { - this.server.destroy(); + try { + logger.fine("Closing npm server in directory <" + serverPortFile.getParent() + "> and port <" + serverPort + ">"); + if (server.isAlive()) { + boolean ended = server.waitFor(5, TimeUnit.SECONDS); + System.out.println("BBB/Closing server at port " + serverPort + " -- " + ended); + if (!ended) { + logger.info("Force-Closing npm server in directory <" + serverPortFile.getParent() + "> and port <" + serverPort + ">"); + server.destroyForcibly().waitFor(); + logger.fine("Force-Closing npm server in directory <" + serverPortFile.getParent() + "> and port <" + serverPort + "> -- Finished"); + } + } + } finally { + NpmResourceHelper.deleteFileIfExists(serverPortFile); } } } 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 2ee7cedfe1..2a71ff893e 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -23,17 +23,22 @@ import java.util.Collections; import java.util.Map; import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterFunc.Closeable; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.Provisioner; import com.diffplug.spotless.ThrowingEx; public class PrettierFormatterStep { + private static final Logger logger = Logger.getLogger(PrettierFormatterStep.class.getName()); + public static final String NAME = "prettier-format"; public static final Map defaultDevDependencies() { @@ -79,18 +84,24 @@ public static class State extends NpmFormatterStepStateBase implements Serializa @Override @Nonnull public FormatterFunc createFormatterFunc() { - try { - ServerProcessInfo prettierRestServer = npmRunServer(); PrettierRestService restService = new PrettierRestService(prettierRestServer.getBaseUrl()); - String prettierConfigOptions = restService.resolveConfig(this.prettierConfig.getPrettierConfigPath(), this.prettierConfig.getOptions()); - return FormatterFunc.Closeable.of(prettierRestServer, input -> restService.format(input, prettierConfigOptions)); + return Closeable.of(() -> endServer(restService, prettierRestServer), input -> restService.format(input, prettierConfigOptions)); } catch (Exception e) { throw ThrowingEx.asRuntime(e); } } + private void endServer(PrettierRestService restService, ServerProcessInfo restServer) throws Exception { + try { + restService.shutdown(); + } catch (Throwable t) { + logger.log(Level.INFO, "Failed to request shutdown of rest service via api. Trying via process.", t); + } + restServer.close(); + } + } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java index 70aaf23d3b..ced08b013f 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java @@ -49,4 +49,8 @@ public String format(String fileContent, String configOptionsJsonString) { return restClient.postJson("/prettier/format", jsonProperties); } + public String shutdown() { + return restClient.post("/shutdown"); + } + } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java index 06eb3d0e1d..c1c8804cb0 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java @@ -44,6 +44,10 @@ String postJson(String endpoint, Map jsonParams) throws SimpleRe return postJson(endpoint, jsonString); } + String post(String endpoint) throws SimpleRestException { + return postJson(endpoint, (String) null); + } + String postJson(String endpoint, String rawJson) throws SimpleRestException { try { URL url = new URL(this.baseUrl + endpoint); @@ -53,9 +57,11 @@ String postJson(String endpoint, String rawJson) throws SimpleRestException { con.setRequestMethod("POST"); con.setRequestProperty("Content-Type", "application/json"); con.setDoOutput(true); - try (OutputStream out = con.getOutputStream()) { - NpmResourceHelper.writeUtf8StringToOutputStream(rawJson, out); - out.flush(); + if (rawJson != null) { + try (OutputStream out = con.getOutputStream()) { + NpmResourceHelper.writeUtf8StringToOutputStream(rawJson, out); + out.flush(); + } } int status = con.getResponseCode(); 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 847c1fa64c..1a948bd366 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java @@ -21,16 +21,22 @@ import java.io.IOException; import java.io.Serializable; import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterFunc.Closeable; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.Provisioner; import com.diffplug.spotless.ThrowingEx; public class TsFmtFormatterStep { + + private static final Logger logger = Logger.getLogger(TsFmtFormatterStep.class.getName()); + public static final String NAME = "tsfmt-format"; @Deprecated @@ -92,11 +98,9 @@ public State(String stepName, Map versions, File buildDir, @Null public FormatterFunc createFormatterFunc() { try { Map tsFmtOptions = unifyOptions(); - ServerProcessInfo tsfmtRestServer = npmRunServer(); TsFmtRestService restService = new TsFmtRestService(tsfmtRestServer.getBaseUrl()); - - return FormatterFunc.Closeable.of(tsfmtRestServer, input -> restService.format(input, tsFmtOptions)); + return Closeable.of(() -> endServer(restService, tsfmtRestServer), input -> restService.format(input, tsFmtOptions)); } catch (Exception e) { throw ThrowingEx.asRuntime(e); } @@ -115,5 +119,14 @@ private Map unifyOptions() { } return unified; } + + private void endServer(TsFmtRestService restService, ServerProcessInfo restServer) throws Exception { + try { + restService.shutdown(); + } catch (Throwable t) { + logger.log(Level.INFO, "Failed to request shutdown of rest service via api. Trying via process.", t); + } + restServer.close(); + } } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java index a847fa074d..a47b608c36 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java @@ -36,4 +36,8 @@ public String format(String fileContent, Map configOptions) { return restClient.postJson("/tsfmt/format", jsonProperties); } + public String shutdown() { + return restClient.post("/shutdown"); + } + } diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json index 89add57b91..113f20bc3f 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json @@ -9,7 +9,8 @@ }, "devDependencies": { ${devDependencies}, - "express": "4.17.1" + "express": "4.17.1", + "@moebius/http-graceful-shutdown": "1.1.0" }, "dependencies": {}, "engines": { diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js index 6fc2e1ef15..d9e2d7c937 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js @@ -1,7 +1,8 @@ +const GracefulShutdownManager = require("@moebius/http-graceful-shutdown").GracefulShutdownManager; const express = require("express"); const app = express(); -app.use(express.json({ limit: "50mb" })); +app.use(express.json({ limit: "50mb" })); const prettier = require("prettier"); const fs = require("fs"); @@ -20,6 +21,14 @@ var listener = app.listen(0, "127.0.0.1", () => { } }); }); +const shutdownManager = new GracefulShutdownManager(listener); + +app.post("/shutdown", (req, res) => { + res.status(200).send("Shutting down"); + setTimeout(function() { + shutdownManager.terminate(() => console.log("graceful shutdown finished.")); + }, 200); +}); app.post("/prettier/config-options", (req, res) => { var config_data = req.body; diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json index df3e247097..d6e5eff3b2 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json @@ -9,7 +9,8 @@ }, "devDependencies": { ${devDependencies}, - "express": "4.17.1" + "express": "4.17.1", + "@moebius/http-graceful-shutdown": "1.1.0" }, "dependencies": {}, "engines": { diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js index e682a1d0d4..8048cfa78c 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js @@ -1,3 +1,4 @@ +const GracefulShutdownManager = require("@moebius/http-graceful-shutdown").GracefulShutdownManager; const express = require("express"); const app = express(); app.use(express.json({ limit: "50mb" })); @@ -21,6 +22,15 @@ var listener = app.listen(0, "127.0.0.1", () => { }); }); +const shutdownManager = new GracefulShutdownManager(listener); + +app.post("/shutdown", (req, res) => { + res.status(200).send("Shutting down"); + setTimeout(function() { + shutdownManager.terminate(() => console.log("graceful shutdown finished.")); + }, 200); +}); + app.post("/tsfmt/format", (req, res) => { var format_data = req.body; tsfmt.processString("spotless-format-string.ts", format_data.file_content, format_data.config_options).then(resultMap => { From 7c91e93d03d3e1dab793953f8d69de552011cb43 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Tue, 9 Jun 2020 19:22:27 +0200 Subject: [PATCH 27/43] Revert "fixing ci build" This reverts commit c0879e11fb81861f9a702a35e1a9111eddb048d8. --- .../diffplug/spotless/maven/MavenRunner.java | 54 +++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenRunner.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenRunner.java index 9eb0e39623..e7fbb88a14 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenRunner.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenRunner.java @@ -17,12 +17,17 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.io.*; -import java.nio.charset.StandardCharsets; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; import java.util.Objects; +import com.diffplug.common.base.Throwables; +import com.diffplug.common.io.ByteStreams; import com.diffplug.spotless.LineEnding; /** @@ -64,22 +69,13 @@ private Result run() throws IOException, InterruptedException { ProcessBuilder builder = new ProcessBuilder(cmds); builder.directory(projectDir); Process process = builder.start(); + // slurp and return the stdout, stderr, and exitValue + Slurper output = new Slurper(process.getInputStream()); + Slurper error = new Slurper(process.getErrorStream()); int exitValue = process.waitFor(); - try (InputStream processOutputStream = process.getInputStream(); InputStream processErrorStream = process.getErrorStream()) { - String output = readFully(processOutputStream); - String error = readFully(processErrorStream); - return new Result(exitValue, output, error); - } - } - - private String readFully(InputStream input) throws IOException { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int numRead; - while ((numRead = input.read(buffer)) != -1) { - output.write(buffer, 0, numRead); - } - return output.toString(StandardCharsets.UTF_8.name()); + output.join(); + error.join(); + return new Result(exitValue, output.result(), error.result()); } /** Runs the command and asserts that exit code is 0. */ @@ -139,4 +135,28 @@ private static List getPlatformCmds(String cmd) { } } + private static class Slurper extends Thread { + private final InputStream input; + private volatile String result; + + Slurper(InputStream input) { + this.input = Objects.requireNonNull(input); + start(); + } + + @Override + public void run() { + try { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + ByteStreams.copy(input, output); + result = output.toString(Charset.defaultCharset().name()); + } catch (Exception e) { + result = Throwables.getStackTraceAsString(e); + } + } + + public String result() { + return result; + } + } } From a3c56142752df4e74ba5a72e10939213489a1143 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Tue, 9 Jun 2020 20:58:17 +0200 Subject: [PATCH 28/43] cleanup accidential sysout checkin --- .../com/diffplug/spotless/npm/NpmFormatterStepStateBase.java | 1 - 1 file changed, 1 deletion(-) 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 ec8a4a5c23..bcdd369e10 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -164,7 +164,6 @@ public void close() throws Exception { logger.fine("Closing npm server in directory <" + serverPortFile.getParent() + "> and port <" + serverPort + ">"); if (server.isAlive()) { boolean ended = server.waitFor(5, TimeUnit.SECONDS); - System.out.println("BBB/Closing server at port " + serverPort + " -- " + ended); if (!ended) { logger.info("Force-Closing npm server in directory <" + serverPortFile.getParent() + "> and port <" + serverPort + ">"); server.destroyForcibly().waitFor(); From 42309c7de4a9f5a3cfd3aa8b168072f518fce650 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 11 Jun 2020 11:43:32 -0700 Subject: [PATCH 29/43] Make the windows build run the npmTest --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9abb5c24c2..efb57d0ff5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -132,6 +132,9 @@ jobs: - run: name: gradlew check command: gradlew check --build-cache + - run: + name: gradlew npmTest + command: ./gradlew npmTest --build-cache - store_test_results: path: testlib/build/test-results/test - store_test_results: From f5e7e6c4b915bdb481c917f7e9e44ea8e739cc61 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 11 Jun 2020 11:54:41 -0700 Subject: [PATCH 30/43] Oops, windows build doesn't want './' --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index efb57d0ff5..4d2eb2398c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -134,7 +134,7 @@ jobs: command: gradlew check --build-cache - run: name: gradlew npmTest - command: ./gradlew npmTest --build-cache + command: gradlew npmTest --build-cache - store_test_results: path: testlib/build/test-results/test - store_test_results: From d319266262e5af6c6414ec8dae0d4087259b464b Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 11 Jun 2020 12:02:17 -0700 Subject: [PATCH 31/43] Oops, we need to store the test results too! --- .circleci/config.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4d2eb2398c..adc2e92d01 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -132,9 +132,6 @@ jobs: - run: name: gradlew check command: gradlew check --build-cache - - run: - name: gradlew npmTest - command: gradlew npmTest --build-cache - store_test_results: path: testlib/build/test-results/test - store_test_results: @@ -143,6 +140,15 @@ jobs: path: plugin-gradle/build/test-results/test - store_test_results: path: plugin-maven/build/test-results/test + - run: + name: gradlew npmTest + command: gradlew npmTest --build-cache + - store_test_results: + path: testlib/build/test-results/npm + - store_test_results: + path: plugin-maven/build/test-results/npm + - store_test_results: + path: plugin-gradle/build/test-results/npm changelog_print: << : *env_gradle steps: From 5764f4ce8019851d3128ffadbdc8a0c49846d102 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 11 Jun 2020 13:07:01 -0700 Subject: [PATCH 32/43] Add a test that recreates the problem I'm seeing. --- .../filetypes/scss/causes500error.dirty | 94 +++++++++++++++++++ .../npm/PrettierFormatterStepTest.java | 15 +++ 2 files changed, 109 insertions(+) create mode 100644 testlib/src/main/resources/npm/prettier/filetypes/scss/causes500error.dirty diff --git a/testlib/src/main/resources/npm/prettier/filetypes/scss/causes500error.dirty b/testlib/src/main/resources/npm/prettier/filetypes/scss/causes500error.dirty new file mode 100644 index 0000000000..330cf4b5c9 --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/filetypes/scss/causes500error.dirty @@ -0,0 +1,94 @@ +///////////////// +// OPEN SOURCE // +///////////////// +#opensource-heading { + @extend .btn-raised; + background-color: white; + padding: 10px; + border-radius: $border-radius-base; + > h3 { + margin: 0; + } + img { + width: 500px; + max-width: 100%; + float: left; + } + ul { + list-style-position: inside; + > li > span { + position: relative; + left: -1em; + } + } +} +#opensource { + $animate-time: 500ms; + $min-width: 300px; + .category { + color: white; + display: block; + background-color: $blue-light; + text-transform: uppercase; + text-align: center; + + &:hover { + background-color: lighten($blue-light, 10%); + cursor: pointer; + text-decoration: none; + } + } + + > #opensource-categories { + margin: 15px 0; + > .category { + @extend .btn-raised; + width: 120px; + float: left; + font-size: 20px; + padding: 5px 10px; + margin: 0 10px 10px 0; + } + } + + > .opensource-lib { + @include transition(all $transition-time ease-in-out); + @extend .btn-raised; + float: left; + width: $min-width; + height: 100px; + padding: 5px; + margin: 0 10px 10px 0; + background-color: white; + color: black; + &:hover { + text-decoration: none; + } + img { + max-height: 90px; + max-width: 90px; + float: left; + } + > h3 { + padding: 0; + margin: 0; + line-height: $font-size-h3; + display: inline; + } + > .categories { + float: right; + font-size: 13px; + width: 60px; + //margin: 2px 0; + padding: 0; + > .category { + width: 100%; + padding: 2px 5px; + margin: 0 0 5px 0; + } + } + > .description { + margin: 0; + } + } +} diff --git a/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java b/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java index 67f5cfdfc9..039451c1c9 100644 --- a/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java @@ -86,6 +86,21 @@ public void parserInferenceIsWorking() throws Exception { stepHarness.testResource(dirtyFile, cleanFile); } } + + @Test + public void recreate500InternalServerError() throws Exception { + FormatterStep formatterStep = PrettierFormatterStep.create( + PrettierFormatterStep.defaultDevDependenciesWithPrettier("2.0.5"), + TestProvisioner.mavenCentral(), + buildDir(), + npmExecutable(), + new PrettierConfig(null, ImmutableMap.of("parser", "postcss"))); + try (StepHarness stepHarness = StepHarness.forStep(formatterStep)) { + stepHarness.testException("npm/prettier/filetypes/scss/causes500error.dirty", exception -> { + exception.hasMessageStartingWith("500: Internal Server Error"); + }); + } + } } @Category(NpmTest.class) From a12343f0158cdf41dd729e4dcb785b95e95cf450 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Wed, 10 Jun 2020 08:37:06 +0200 Subject: [PATCH 33/43] bump version numbers to latest --- .../java/com/diffplug/spotless/npm/PrettierFormatterStep.java | 2 +- .../java/com/diffplug/spotless/npm/TsFmtFormatterStep.java | 4 ++-- plugin-gradle/README.md | 4 ++-- .../main/resources/npm/prettier/filetypes/css/.prettierrc.yml | 2 +- .../prettier/filetypes/javascript-es5/javascript-es5.clean | 4 ++-- .../prettier/filetypes/javascript-es6/javascript-es6.clean | 4 ++-- .../resources/npm/prettier/filetypes/scss/.prettierrc.yml | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) 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 2a71ff893e..d9c470b5d9 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -42,7 +42,7 @@ public class PrettierFormatterStep { public static final String NAME = "prettier-format"; public static final Map defaultDevDependencies() { - return defaultDevDependenciesWithPrettier("1.16.4"); + return defaultDevDependenciesWithPrettier("2.0.5"); } public static final Map defaultDevDependenciesWithPrettier(String version) { 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 1a948bd366..e815ea1f63 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java @@ -59,8 +59,8 @@ public static Map defaultDevDependencies() { public static Map defaultDevDependenciesWithTsFmt(String typescriptFormatter) { TreeMap defaults = new TreeMap<>(); defaults.put("typescript-formatter", typescriptFormatter); - defaults.put("typescript", "3.3.3"); - defaults.put("tslint", "5.12.1"); + defaults.put("typescript", "3.9.5"); + defaults.put("tslint", "6.1.2"); return defaults; } diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index ea07c07777..5b2835d70b 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -425,7 +425,7 @@ spotless { target '**/*.css', '**/*.scss' // at least provide the parser to use - prettier().config(['parser': 'postcss']) + prettier().config(['parser': 'css']) // prettier('1.16.4') to specify specific version of prettier // prettier(['my-prettier-fork': '1.16.4']) to specify exactly which npm packages to use @@ -447,7 +447,7 @@ spotless { prettier().configFile('/path-to/.prettierrc.yml') // or provide both (config options take precedence over configFile options) - prettier().config(['parser': 'postcss']).configFile('path-to/.prettierrc.yml') + prettier().config(['parser': 'css']).configFile('path-to/.prettierrc.yml') } } ``` diff --git a/testlib/src/main/resources/npm/prettier/filetypes/css/.prettierrc.yml b/testlib/src/main/resources/npm/prettier/filetypes/css/.prettierrc.yml index 9919fdc796..1141920e4e 100644 --- a/testlib/src/main/resources/npm/prettier/filetypes/css/.prettierrc.yml +++ b/testlib/src/main/resources/npm/prettier/filetypes/css/.prettierrc.yml @@ -1 +1 @@ -parser: postcss +parser: css diff --git a/testlib/src/main/resources/npm/prettier/filetypes/javascript-es5/javascript-es5.clean b/testlib/src/main/resources/npm/prettier/filetypes/javascript-es5/javascript-es5.clean index 86cbfca970..29002f6b05 100644 --- a/testlib/src/main/resources/npm/prettier/filetypes/javascript-es5/javascript-es5.clean +++ b/testlib/src/main/resources/npm/prettier/filetypes/javascript-es5/javascript-es5.clean @@ -18,7 +18,7 @@ var numbers = [ 17, 18, 19, - 20 + 20, ]; var p = { @@ -26,7 +26,7 @@ var p = { last: "Pan", get fullName() { return this.first + " " + this.last; - } + }, }; var str = "Hello, world!"; diff --git a/testlib/src/main/resources/npm/prettier/filetypes/javascript-es6/javascript-es6.clean b/testlib/src/main/resources/npm/prettier/filetypes/javascript-es6/javascript-es6.clean index 6737291a0a..d4e982d69f 100644 --- a/testlib/src/main/resources/npm/prettier/filetypes/javascript-es6/javascript-es6.clean +++ b/testlib/src/main/resources/npm/prettier/filetypes/javascript-es6/javascript-es6.clean @@ -18,7 +18,7 @@ var numbers = [ 17, 18, 19, - 20 + 20, ]; const p = { @@ -26,7 +26,7 @@ const p = { last: "Pan", get fullName() { return this.first + " " + this.last; - } + }, }; const str = "Hello, world!"; diff --git a/testlib/src/main/resources/npm/prettier/filetypes/scss/.prettierrc.yml b/testlib/src/main/resources/npm/prettier/filetypes/scss/.prettierrc.yml index 9919fdc796..1141920e4e 100644 --- a/testlib/src/main/resources/npm/prettier/filetypes/scss/.prettierrc.yml +++ b/testlib/src/main/resources/npm/prettier/filetypes/scss/.prettierrc.yml @@ -1 +1 @@ -parser: postcss +parser: css From 2bc0464789e74cf2597b9dc64f436dfe39c60861 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Thu, 11 Jun 2020 20:41:37 +0200 Subject: [PATCH 34/43] improve robustness and error feedback --- .../diffplug/spotless/npm/SimpleRestClient.java | 15 ++++++++++++--- .../com/diffplug/spotless/npm/prettier-serve.js | 11 ++++++++++- .../com/diffplug/spotless/npm/tsfmt-serve.js | 2 ++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java index c1c8804cb0..5958135d09 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java @@ -19,6 +19,7 @@ import java.io.BufferedInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; @@ -67,7 +68,7 @@ String postJson(String endpoint, String rawJson) throws SimpleRestException { int status = con.getResponseCode(); if (status != 200) { - throw new SimpleRestResponseException(status, con.getResponseMessage(), "Unexpected response status code."); + throw new SimpleRestResponseException(status, readError(con), "Unexpected response status code at " + endpoint); } String response = readResponse(con); @@ -77,8 +78,16 @@ String postJson(String endpoint, String rawJson) throws SimpleRestException { } } + private String readError(HttpURLConnection con) throws IOException { + return readInputStream(con.getErrorStream()); + } + private String readResponse(HttpURLConnection con) throws IOException { - try (BufferedInputStream input = new BufferedInputStream(con.getInputStream())) { + return readInputStream(con.getInputStream()); + } + + private String readInputStream(InputStream inputStream) throws IOException { + try (BufferedInputStream input = new BufferedInputStream(inputStream)) { return NpmResourceHelper.readUtf8StringFromInputStream(input); } } @@ -121,7 +130,7 @@ public String getExceptionMessage() { @Override public String getMessage() { - return String.format("%s: %s (%s)", getStatusCode(), getResponseMessage(), getExceptionMessage()); + return String.format("%s [HTTP %s] -- (%s)", getExceptionMessage(), getStatusCode(), getResponseMessage()); } } diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js index d9e2d7c937..28e591f2ac 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js @@ -40,18 +40,27 @@ app.post("/prettier/config-options", (req, res) => { .resolveConfig(undefined, { config: prettier_config_path }) .then(options => { var mergedConfigOptions = mergeConfigOptions(options, prettier_config_options); + res.set("Content-Type", "application/json") res.json(mergedConfigOptions); }) .catch(reason => res.status(501).send("Exception while resolving config_file_path: " + reason)); return; } + res.set("Content-Type", "application/json") res.json(prettier_config_options); }); app.post("/prettier/format", (req, res) => { var format_data = req.body; - var formatted_file_content = prettier.format(format_data.file_content, format_data.config_options); + var formatted_file_content = ""; + try { + console.log("format_data", format_data); + formatted_file_content = prettier.format(format_data.file_content, format_data.config_options); + } catch(err) { + res.status(501).send("Error while formatting: " + err); + return; + } res.set("Content-Type", "text/plain"); res.send(formatted_file_content); }); diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js index 8048cfa78c..b25048d410 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js @@ -55,5 +55,7 @@ app.post("/tsfmt/format", (req, res) => { } res.set("Content-Type", "text/plain"); res.send(resultMap.dest); + }).catch(reason => { + res.status(501).send(reason); }); }); From 6f8c5506bae0a6f1f932a33c3f2a814b225fdd28 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Thu, 11 Jun 2020 20:46:43 +0200 Subject: [PATCH 35/43] adding a test to prove that we can support prettier-plugins now --- .../spotless/PrettierIntegrationTest.java | 32 +++++++++++++++- .../npm/prettier/plugins/java-test.clean | 37 +++++++++++++++++++ .../npm/prettier/plugins/java-test.dirty | 30 +++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 testlib/src/main/resources/npm/prettier/plugins/java-test.clean create mode 100644 testlib/src/main/resources/npm/prettier/plugins/java-test.dirty 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 a371761a53..7d4905683e 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 @@ -17,6 +17,8 @@ import java.io.IOException; +import org.assertj.core.api.Assertions; +import org.gradle.testkit.runner.BuildResult; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -41,7 +43,8 @@ public void useInlineConfig() throws IOException { " }", "}"); setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); - gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + 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"); } @@ -60,8 +63,33 @@ public void useFileConfig() throws IOException { " }", "}"); setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); - gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + 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 + public void useJavaCommunityPlugin() throws IOException { + setFile("build.gradle").toLines( + "buildscript { repositories { mavenCentral() } }", + "plugins {", + " id 'com.diffplug.gradle.spotless'", + "}", + "def prettierConfig = [:]", + "prettierConfig['tabWidth'] = 4", + "prettierConfig['parser'] = 'java'", + "def prettierPackages = [:]", + "prettierPackages['prettier'] = '2.0.5'", + "prettierPackages['prettier-plugin-java'] = '0.8.0'", + "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").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("JavaTest.java").sameAsResource("npm/prettier/plugins/java-test.clean"); + } } diff --git a/testlib/src/main/resources/npm/prettier/plugins/java-test.clean b/testlib/src/main/resources/npm/prettier/plugins/java-test.clean new file mode 100644 index 0000000000..c45ffe5183 --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/plugins/java-test.clean @@ -0,0 +1,37 @@ +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +public class JavaTest { + private static final String NAME = "JavaTest"; + + private List strings = new ArrayList<>(); + + public JavaTest(String... strings) { + this.strings.addAll(Arrays.asList(strings)); + } + + /** + * Join using char. + * @param joiner the char to use for joining. + * @return the joined string. + */ + public String join(char joiner) { + return String.join(joiner, strings); + } + + public void operateOn(Consumer> consumer) { + // test comment + consumer.accept(strings); + } + + public static void main(String[] args) { + JavaTest javaTest = new JavaTest("1", "2", "3"); + System.out.println("joined: " + javaTest.join(',')); + StringBuilder builder = new StringBuilder(); + javaTest.operateOn( + strings -> builder.append(String.join("---", strings)) + ); + } +} diff --git a/testlib/src/main/resources/npm/prettier/plugins/java-test.dirty b/testlib/src/main/resources/npm/prettier/plugins/java-test.dirty new file mode 100644 index 0000000000..ebadbfe112 --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/plugins/java-test.dirty @@ -0,0 +1,30 @@ +import java.util.ArrayList; + + +import java.util.Arrays;import java.util.List; +import java.util.function.Consumer; + +public class JavaTest { private static final String NAME = "JavaTest"; + + private List strings = new ArrayList<>(); + + public JavaTest( String ... strings) { + this.strings .addAll(Arrays.asList(strings));; + } + +/** +* Join using char. +* @param joiner the char to use for joining. +* @return the joined string. +*/ + public String + join(char joiner ) { + return String.join(joiner, strings); + } + + public void operateOn( Consumer> consumer) { +// test comment + consumer.accept( strings); + } + + public static void main(String[] args) {JavaTest javaTest = new JavaTest("1", "2", "3");System.out.println("joined: " + javaTest.join(',' ));StringBuilder builder = new StringBuilder();javaTest.operateOn((strings) -> builder.append(String.join("---", strings)));}} \ No newline at end of file From 0808fc2886a0bde60af4c445df6575ad205618be Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Thu, 11 Jun 2020 22:30:24 +0200 Subject: [PATCH 36/43] documenting the community-plugin benefit and adding more examples --- .../diffplug/spotless/npm/prettier-serve.js | 1 - plugin-gradle/README.md | 22 +- .../spotless/PrettierIntegrationTest.java | 25 + .../resources/npm/prettier/plugins/php.clean | 500 ++++++++++++++++++ .../resources/npm/prettier/plugins/php.dirty | 295 +++++++++++ 5 files changed, 841 insertions(+), 2 deletions(-) create mode 100644 testlib/src/main/resources/npm/prettier/plugins/php.clean create mode 100644 testlib/src/main/resources/npm/prettier/plugins/php.dirty diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js index 28e591f2ac..351ef73f9a 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js @@ -55,7 +55,6 @@ app.post("/prettier/format", (req, res) => { var formatted_file_content = ""; try { - console.log("format_data", format_data); formatted_file_content = prettier.format(format_data.file_content, format_data.config_options); } catch(err) { res.status(501).send("Error while formatting: " + err); diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index 5b2835d70b..757ec52e15 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -468,8 +468,28 @@ spotless { } ``` + +### Using community plugins for prettier + +Since spotless uses the actual npm prettier package behind the scenes, it is possible to use prettier with +community-plugins in order to support even more file types. + +```gradle +spotless { + format 'prettierJava', { + target '**/*.java' + + prettier(['prettier': '2.0.5', 'prettier-plugin-java': '0.8.0']).config(['parser': 'java', 'tabWidth': 4]) + } + format 'php', { + target '**/*.php' + prettier(['prettier': '2.0.5', '@prettier/plugin-php': '0.14.2']).config(['parser': 'php', 'tabWidth': 3]) + } +} +``` + -Prettier can also be applied from within the [typescript config block](#typescript-formatter): +### Note: Prettier can also be applied from within the [typescript config block](#typescript-formatter) ```gradle spotless { 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 7d4905683e..a3955b476e 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 @@ -92,4 +92,29 @@ public void useJavaCommunityPlugin() throws IOException { Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); assertFile("JavaTest.java").sameAsResource("npm/prettier/plugins/java-test.clean"); } + + @Test + public void usePhpCommunityPlugin() throws IOException { + setFile("build.gradle").toLines( + "buildscript { repositories { mavenCentral() } }", + "plugins {", + " id 'com.diffplug.gradle.spotless'", + "}", + "def prettierConfig = [:]", + "prettierConfig['tabWidth'] = 3", + "prettierConfig['parser'] = 'php'", + "def prettierPackages = [:]", + "prettierPackages['prettier'] = '2.0.5'", + "prettierPackages['@prettier/plugin-php'] = '0.14.2'", + "spotless {", + " format 'php', {", + " target 'php-example.php'", + " prettier(prettierPackages).config(prettierConfig)", + " }", + "}"); + setFile("php-example.php").toResource("npm/prettier/plugins/php.dirty"); + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("php-example.php").sameAsResource("npm/prettier/plugins/php.clean"); + } } diff --git a/testlib/src/main/resources/npm/prettier/plugins/php.clean b/testlib/src/main/resources/npm/prettier/plugins/php.clean new file mode 100644 index 0000000000..4c8710bbee --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/plugins/php.clean @@ -0,0 +1,500 @@ +other = $test_string; + $this->current_version = $current_version ?: new Content_Version_Model(); + self::$staticTest = $test_int; + } + + public static function test_static_constructor( + $test, + $test_int, + $test_string + ) { + $model = new self($test, $test_int, $test_string); + $model = new self( + $really_really_really_really_really_really_really_really_long_array, + $test_int, + $test_string + ); + return $model; + } + + public function test_pass_by_reference(&$test) + { + $test + 1; + } + + /** + * This is a function + */ + private function hi($input) + { + $test = 1; + + //testing line spacing + $other_test = 2; + + $one_more_test = 3; + return $input . $this->test; + } + + public function reallyReallyReallyReallyReallyReallyReallyLongMethodName( + $input, + $otherInput = 1 + ) { + return true; + } + + // doc test + public static function testStaticFunction($input) + { + return self::$staticTest[0]; + } + + public function returnTypeTest(): string + { + return 'hi'; + } + + final public static function bar() + { + // Nothing + } + + abstract protected function zim(); + + public function method(iterable $iterable): array + { + // Parameter broadened and return type narrowed. + } + + public function method1() + { + return 'hi'; + } + + public function method2() + { + return 'hi'; + } + + public function method3() + { + return 'hi'; + } + + public function testReturn(?string $name): ?string + { + return $name; + } + + public function swap(&$left, &$right): void + { + if ($left === $right) { + return; + } + + $tmp = $left; + $left = $right; + $right = $tmp; + } + + public function test(object $obj): object + { + return new SplQueue(); + } + + public function longLongAnotherFunction( + string $foo, + string $bar, + int $baz + ): string { + return 'foo'; + } + + public function longLongAnotherFunctionOther( + string $foo, + string $bar, + int $baz + ) { + return 'foo'; + } + + public function testReturnTypeDeclaration(): object + { + return new SplQueue(); + } + + public function testReturnTypeDeclarationOther(): object + { + return new SplQueue(); + } +} + +$this->something->method( + $argument, + $this->more->stuff($this->even->more->things->complicatedMethod()) +); + +class A +{ +} + +$someVar = new ReaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongClassName(); + +class ClassName extends ParentClass implements \ArrayAccess, \Countable +{ + // constants, properties, methods +} + +class FooBar +{ + public $property; + public $property2; + public function method() + { + } + public function method2() + { + } +} + +class FooBarFoo +{ + public function fooBarBaz($x, $y, $z, $foo, $bar) + { + /* Comment */ + } +} + +class ClassName extends ParentClass implements InterfaceClass +{ +} + +class ClassName extends ParentClass implements + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 +{ +} + +class ClassName extends ParentClass implements + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 +{ +} + +class ClassName extends VeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 implements + InterfaceClass +{ +} + +class ClassName + extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 + implements InterfaceClass +{ +} + +class ClassName + extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 + implements VeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 +{ +} + +class ClassName + extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 + implements VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 +{ +} + +class ClassName extends ParentClass implements + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1, + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName2, + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName3 +{ +} + +class ClassName extends VeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 implements + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1, + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName2, + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName3 +{ +} + +class ClassName + extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 + implements + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1, + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName2, + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName3 +{ +} + +class VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 + extends ParentClass + implements InterfaceClass +{ +} + +class Custom_Plugin_NotificationPlaceholderSource extends + Notification_Manager_DefaultPlaceholderSource +{ +} + +class field extends \models\base +{ + protected function pre_save($input, $fields) + { + $input['configs'] = json_encode( + array_merge( + $configs, + $field_type->process_field_config_from_user($input['definition']) + ) + ); + unset($input['definition']); + } +} + +class test +{ + public function test_method() + { + $customer = (object) ['name' => 'Bob']; + $job = (object) ['customer' => $customer]; + + return "The customer for that job, {$job->customer->name} has an error that shows up after the line gets waaaaay toooo long."; + } +} + +class EmptyClass +{ +} + +class EmptyClassWithComments +{ + /* Comment */ +} + +class MyClass implements MyOtherClass +{ +} + +class MyClass implements MyOtherClass, MyOtherClass1, MyOtherClass2 +{ +} + +class MyClass implements + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass +{ +} + +class VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass + implements VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyOtherClass +{ +} + +class VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass + implements MyOtherClass +{ +} + +class MyClass implements + MyOtherClass, + MyOtherClass, + MyOtherOtherOtherClass, + MyOtherOtherOtherOtherClass +{ +} + +class VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass + implements + MyOtherClass, + MyOtherClass, + MyOtherOtherOtherClass, + MyOtherOtherOtherOtherClass +{ +} + +class EmptyClass extends MyOtherClass +{ +} + +class EmptyClass extends + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass +{ +} + +class VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass + extends EmptyClass +{ +} + +class VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass + extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass +{ +} + +class MyClass extends MyOtherClass implements MyI +{ +} + +class MyClass extends MyOtherClass implements MyI, MyII, MyIII +{ +} + +class MyClass extends MyOtherClass implements + VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass +{ +} + +class MyClass extends MyOtherClass implements + MyInterface, + MyOtherInterface, + MyOtherOtherInterface +{ +} + +class MyClass + extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass + implements MyI +{ +} + +class MyClass + extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass + implements + MyI, + MyII, + MyIII +{ +} + +class MyClass + extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass + implements VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass +{ +} + +class MyClass + extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass + implements + MyI, + MyII, + MyIII +{ +} + +final class BaseClass +{ +} + +abstract class BaseClass +{ +} + +final class BaseClass extends MyOtherClass +{ +} + +abstract class BaseClass extends MyOtherClass +{ +} + +final class BaseClass extends MyOtherVeryVeryVeryVeVeryVeryVeryVeryVeryLongClass +{ +} + +abstract class BaseClass extends MyOtherVeryVeryVeryVeVeryVeryVeryVVeryLongClass +{ +} + +final class BaseClass extends + MyOtherVeryVeryVeryVeVeryVeryVeryVeryVeryLongClass1 +{ +} + +abstract class BaseClass extends + MyOtherVeryVeryVeryVeVeryVeryVeryVVeryLongClass1 +{ +} + +final class BaseClass extends MyOtherClass implements + MyInterface, + MyOtherInterface, + MyOtherOtherInterface +{ +} + +abstract class BaseClass extends MyOtherClass implements + MyInterface, + MyOtherInterface, + MyOtherOtherInterface +{ +} + +class User +{ + public int $id; + public string $name; + public ?string $b = 'foo'; + private Foo $prop; + protected static string $static = 'default'; + + public function __construct(int $id, string $name) + { + $this->id = $id; + $this->name = $name; + } +} diff --git a/testlib/src/main/resources/npm/prettier/plugins/php.dirty b/testlib/src/main/resources/npm/prettier/plugins/php.dirty new file mode 100644 index 0000000000..180e059c84 --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/plugins/php.dirty @@ -0,0 +1,295 @@ +other = $test_string; + $this->current_version = $current_version ?: new Content_Version_Model(); + self::$staticTest = $test_int; + } + + public static function test_static_constructor($test, $test_int, $test_string) { + $model = new self($test, $test_int, $test_string); + $model = new self($really_really_really_really_really_really_really_really_long_array, $test_int, $test_string); + return $model; + } + + public function test_pass_by_reference(&$test) + { + $test + 1; + } + + /** + * This is a function + */ + private function hi($input) { + $test = 1; + + //testing line spacing + $other_test = 2; + + + $one_more_test = 3; + return $input . $this->test; + + } + + public function reallyReallyReallyReallyReallyReallyReallyLongMethodName($input, $otherInput = 1) { + return true; + } + + // doc test + public static function testStaticFunction($input) { + return self::$staticTest[0]; + } + + public function returnTypeTest() : string + { + return 'hi'; + } + + final public static function bar() + { + // Nothing + } + + abstract protected function zim(); + + public function method(iterable $iterable): array { + // Parameter broadened and return type narrowed. + } + + public function method1() { return 'hi'; } + + public function method2() { + return 'hi'; } + + public function method3() + { return 'hi'; } + + public function testReturn(?string $name): ?string + { + return $name; + } + + public function swap(&$left, &$right): void + { + if ($left === $right) { + return; + } + + $tmp = $left; + $left = $right; + $right = $tmp; + } + + public function test(object $obj): object + { + return new SplQueue(); + } + + public function longLongAnotherFunction( + string $foo, + string $bar, + int $baz + ): string { + return 'foo'; + } + + public function longLongAnotherFunctionOther( + string $foo, + string $bar, + int $baz + ) { + return 'foo'; + } + + public function testReturnTypeDeclaration() : object + { + return new SplQueue(); + } + + public function testReturnTypeDeclarationOther() + : + object + { + return new SplQueue(); + } +} + +$this->something->method($argument, $this->more->stuff($this->even->more->things->complicatedMethod())); + +class A {} + +$someVar = new ReaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongClassName(); + +class ClassName + +extends ParentClass + +implements \ArrayAccess, \Countable + +{ + + // constants, properties, methods + +} + +class FooBar { public $property; public $property2; public function method() {} public function method2() {} } + +class FooBarFoo +{ + public function fooBarBaz ( $x,$y ,$z, $foo , $bar ) { /* Comment */ } +} + +class ClassName extends ParentClass implements InterfaceClass {} + +class ClassName extends ParentClass implements VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 {} + +class ClassName extends ParentClass implements VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 {} + +class ClassName extends VeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 implements InterfaceClass {} + +class ClassName extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 implements InterfaceClass {} + +class ClassName extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 implements VeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 {} + +class ClassName extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 implements VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 {} + +class ClassName extends ParentClass implements VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1,VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName2, VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName3 {} + +class ClassName extends VeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 implements VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1,VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName2, VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName3 {} + +class ClassName extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 implements VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1,VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName2, VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName3 {} + +class VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFileName1 extends ParentClass implements InterfaceClass {} + +class Custom_Plugin_NotificationPlaceholderSource extends Notification_Manager_DefaultPlaceholderSource {} + +class field extends \models\base +{ + protected function pre_save( $input, $fields ) { + $input['configs'] = json_encode( array_merge( $configs, $field_type->process_field_config_from_user( $input['definition'] ) ) ); + unset( $input['definition'] ); + } +} + +class test { + public function test_method() { + $customer = (object) [ 'name' => 'Bob' ]; + $job = (object) [ 'customer' => $customer ]; + + return "The customer for that job, {$job->customer->name} has an error that shows up after the line gets waaaaay toooo long."; + } +} + +class EmptyClass {} + +class EmptyClassWithComments { /* Comment */ } + +class MyClass implements MyOtherClass {} + +class MyClass implements MyOtherClass, MyOtherClass1, MyOtherClass2 {} + +class MyClass implements VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass {} + +class VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass implements VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyOtherClass {} + +class VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass implements MyOtherClass {} + +class MyClass implements MyOtherClass, MyOtherClass, MyOtherOtherOtherClass, MyOtherOtherOtherOtherClass {} + +class VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass implements MyOtherClass, MyOtherClass, MyOtherOtherOtherClass, MyOtherOtherOtherOtherClass {} + +class EmptyClass extends MyOtherClass {} + +class EmptyClass extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass {} + +class VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass extends EmptyClass {} + +class VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass {} + +class MyClass extends MyOtherClass implements MyI {} + +class MyClass extends MyOtherClass implements MyI, MyII, MyIII {} + +class MyClass extends MyOtherClass implements VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass {} + +class MyClass extends MyOtherClass implements MyInterface, MyOtherInterface, MyOtherOtherInterface {} + +class MyClass extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass implements MyI {} + +class MyClass extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass implements MyI, MyII, MyIII {} + +class MyClass extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass implements VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass {} + +class MyClass extends VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongMyClass implements MyI, MyII, MyIII {} + +final class BaseClass {} + +abstract class BaseClass {} + +final class BaseClass extends MyOtherClass {} + +abstract class BaseClass extends MyOtherClass {} + +final class BaseClass extends MyOtherVeryVeryVeryVeVeryVeryVeryVeryVeryLongClass {} + +abstract class BaseClass extends MyOtherVeryVeryVeryVeVeryVeryVeryVVeryLongClass {} + +final class BaseClass extends MyOtherVeryVeryVeryVeVeryVeryVeryVeryVeryLongClass1 {} + +abstract class BaseClass extends MyOtherVeryVeryVeryVeVeryVeryVeryVVeryLongClass1 {} + +final class BaseClass extends MyOtherClass implements MyInterface, MyOtherInterface, MyOtherOtherInterface {} + +abstract class BaseClass extends MyOtherClass implements MyInterface, MyOtherInterface, MyOtherOtherInterface {} + +class User { + public int $id; + public string $name; + public ?string $b = 'foo'; + private Foo $prop; + protected static string $static = 'default'; + + public function __construct(int $id, string $name) { + $this->id = $id; + $this->name = $name; + } +} \ No newline at end of file From e2981e522c6d66aa752c3a2ac30238f4a8d209f4 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Fri, 12 Jun 2020 07:23:04 +0200 Subject: [PATCH 37/43] PR feedback: try to avoid `**/*.filetype` as target examples --- plugin-gradle/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index 757ec52e15..25f4966dd2 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -138,13 +138,13 @@ See [ECLIPSE_SCREENSHOTS](../ECLIPSE_SCREENSHOTS.md) for screenshots that demons ### Applying to Android Java source -Be sure to add `target '**/*.java'` otherwise spotless will not detect Java code inside Android modules. +Be sure to add `target 'src/*/java/**/*.java'` otherwise spotless will not detect Java code inside Android modules. ```gradle spotless { java { // ... - target '**/*.java' + target 'src/*/java/**/*.java' // ... } } @@ -422,7 +422,7 @@ To use prettier, you first have to specify the files that you want it to apply t ```gradle spotless { format 'styling', { - target '**/*.css', '**/*.scss' + target 'src/*/webapp/**/*.css', 'src/*/webapp/**/*.scss', 'app/**/*.css', 'app/**/*.scss' // at least provide the parser to use prettier().config(['parser': 'css']) @@ -442,7 +442,7 @@ It is also possible to specify the config via file: ```gradle spotless { format 'styling', { - target '**/*.css', '**/*.scss' + target 'src/*/webapp/**/*.css', 'src/*/webapp/**/*.scss', 'app/**/*.css', 'app/**/*.scss' prettier().configFile('/path-to/.prettierrc.yml') @@ -477,12 +477,12 @@ community-plugins in order to support even more file types. ```gradle spotless { format 'prettierJava', { - target '**/*.java' + target 'src/*/java/**/*.java' prettier(['prettier': '2.0.5', 'prettier-plugin-java': '0.8.0']).config(['parser': 'java', 'tabWidth': 4]) } format 'php', { - target '**/*.php' + target 'src/**/*.php' prettier(['prettier': '2.0.5', '@prettier/plugin-php': '0.14.2']).config(['parser': 'php', 'tabWidth': 3]) } } From 8eff9245760a54fe70b5ecf181cc64b1322e88b4 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Fri, 12 Jun 2020 07:26:51 +0200 Subject: [PATCH 38/43] PR feedback: add links to prettier plugins --- plugin-gradle/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index 25f4966dd2..f77262c6b0 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -469,10 +469,10 @@ spotless { ``` -### Using community plugins for prettier +### Using plugins for prettier Since spotless uses the actual npm prettier package behind the scenes, it is possible to use prettier with -community-plugins in order to support even more file types. +[plugins](https://prettier.io/docs/en/plugins.html#official-plugins) or [community-plugins](https://www.npmjs.com/search?q=prettier-plugin) in order to support even more file types. ```gradle spotless { From dd6f120032908ca355b0dbc3091af86ac9ec0fc5 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Fri, 12 Jun 2020 11:13:30 +0200 Subject: [PATCH 39/43] add support for prettier-plugins to maven-builds and document in README --- plugin-maven/README.md | 74 +++++++++++++++++-- .../spotless/maven/generic/Prettier.java | 70 +++++++++++------- .../prettier/PrettierFormatStepTest.java | 29 +++++++- 3 files changed, 141 insertions(+), 32 deletions(-) diff --git a/plugin-maven/README.md b/plugin-maven/README.md index 65223f648e..8e2e6dbab1 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -287,13 +287,21 @@ To use prettier, you first have to specify the files that you want it to apply t - + 1.19.0 1.19.0 - + + + prettier + 2.0.5 + + + @prettier/plugin-php + 0.14.2 + + ${basedir}/path/to/configfile @@ -315,6 +323,62 @@ Supported config file variants are documented on [prettier.io](https://prettier. To apply prettier to more kinds of files, just add more formats. + +### Using plugins for prettier + +Since spotless uses the actual npm prettier package behind the scenes, it is possible to use prettier with +[plugins](https://prettier.io/docs/en/plugins.html#official-plugins) or [community-plugins](https://www.npmjs.com/search?q=prettier-plugin) in order to support even more file types. + +```xml + + + + + + src/*/java/**/*.java + + + + + 2.0.5 + 0.8.0 + + + 4 + java + + + + + + + + src/**/*.php + + + + + + + prettier + 2.0.5 + + + @prettier/plugin-php + 0.14.2 + + + + 3 + php + + + + + + +``` + ### Prerequisite: prettier requires a working NodeJS version Prettier, like tsfmt, is based on NodeJS, so to use it, a working NodeJS installation (especially npm) is required on the host running spotless. @@ -326,9 +390,7 @@ Spotless will try to auto-discover an npm installation. If that is not working f ... ``` -Spotless uses npm to install necessary packages locally. It runs prettier using [J2V8](https://github.com/eclipsesource/J2V8) internally after that. -Development for J2V8 for non android envs is stopped (for Windows since J2V8 4.6.0 and Unix 4.8.0), therefore Prettier is limited to <= v1.19.0 as newer versions -use ES6 feature and that needs a newer J2V8 version. +Spotless uses npm to install necessary packages and to run the prettier formatter after that. diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Prettier.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Prettier.java index 7c2e12eea5..cfd31d9294 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Prettier.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Prettier.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package com.diffplug.spotless.maven.generic; import java.io.File; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; import org.apache.maven.plugins.annotations.Parameter; @@ -29,12 +29,17 @@ public class Prettier implements FormatterStepFactory { + public static final String ERROR_MESSAGE_ONLY_ONE_CONFIG = "must specify exactly one prettierVersion, devDependencies or devDependencyProperties"; + @Parameter private String prettierVersion; @Parameter private Map devDependencies; + @Parameter + private Properties devDependencyProperties; + @Parameter private Map config; @@ -48,17 +53,17 @@ public class Prettier implements FormatterStepFactory { public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { // check if config is only setup in one way - if (this.prettierVersion != null && this.devDependencies != null) { + if (moreThanOneNonNull(this.prettierVersion, this.devDependencies, this.devDependencyProperties)) { throw onlyOneConfig(); } - - // set dev dependencies if (devDependencies == null) { - if (prettierVersion == null || prettierVersion.isEmpty()) { - devDependencies = PrettierFormatterStep.defaultDevDependencies(); - } else { - devDependencies = PrettierFormatterStep.defaultDevDependenciesWithPrettier(prettierVersion); - } + devDependencies = PrettierFormatterStep.defaultDevDependencies(); // fallback + } + + if (prettierVersion != null && !prettierVersion.isEmpty()) { + this.devDependencies = PrettierFormatterStep.defaultDevDependenciesWithPrettier(prettierVersion); + } else if (devDependencyProperties != null) { + this.devDependencies = dependencyPropertiesAsMap(); } File npm = npmExecutable != null ? stepConfig.getFileLocator().locateFile(npmExecutable) : null; @@ -73,19 +78,20 @@ public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { Map configInline; if (config != null) { - configInline = new LinkedHashMap<>(); - // try to parse string values as integers or booleans - for (Map.Entry e : config.entrySet()) { - try { - configInline.put(e.getKey(), Integer.parseInt(e.getValue())); - } catch (NumberFormatException ignore) { - try { - configInline.put(e.getKey(), Boolean.parseBoolean(e.getValue())); - } catch (IllegalArgumentException ignore2) { - configInline.put(e.getKey(), e.getValue()); - } - } - } + configInline = config.entrySet().stream() + .map(entry -> { + try { + Integer value = Integer.parseInt(entry.getValue()); + return new AbstractMap.SimpleEntry<>(entry.getKey(), value); + } catch (NumberFormatException ignore) { + // ignored + } + if (Boolean.TRUE.toString().equalsIgnoreCase(entry.getValue()) || Boolean.FALSE.toString().equalsIgnoreCase(entry.getValue())) { + return new AbstractMap.SimpleEntry<>(entry.getKey(), Boolean.parseBoolean(entry.getValue())); + } + return entry; + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new)); } else { configInline = null; } @@ -96,7 +102,21 @@ public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { return PrettierFormatterStep.create(devDependencies, stepConfig.getProvisioner(), buildDir, npm, prettierConfig); } + private boolean moreThanOneNonNull(Object... objects) { + return Arrays.stream(objects) + .filter(Objects::nonNull) + .filter(o -> !(o instanceof String) || !((String) o).isEmpty()) // if it is a string, it should not be empty + .count() > 1; + } + + private Map dependencyPropertiesAsMap() { + return this.devDependencyProperties.stringPropertyNames() + .stream() + .map(name -> new AbstractMap.SimpleEntry<>(name, this.devDependencyProperties.getProperty(name))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + private static IllegalArgumentException onlyOneConfig() { - return new IllegalArgumentException("must specify exactly one configFile or config"); + return new IllegalArgumentException(ERROR_MESSAGE_ONLY_ONE_CONFIG); } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java index 772080ea31..a0b80baa1c 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java @@ -19,6 +19,7 @@ import java.io.IOException; +import com.diffplug.spotless.maven.generic.Prettier; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -93,6 +94,32 @@ public void unique_dependency_config() throws Exception { ""); MavenRunner.Result result = mavenRunner().withArguments("spotless:apply").runHasError(); - assertThat(result.output()).contains("must specify exactly one configFile or config"); + assertThat(result.output()).contains(Prettier.ERROR_MESSAGE_ONLY_ONE_CONFIG); + } + + @Test + public void custom_plugin() throws Exception { + writePomWithFormatSteps( + "php-example.php", + "", + " ", + " ", + " prettier", + " 2.0.5", + " ", + " ", + " @prettier/plugin-php", + " 0.14.2", + " ", + " ", + " ", + " 3", + " php", + " ", + ""); + + setFile("php-example.php").toResource("npm/prettier/plugins/php.dirty"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("php-example.php").sameAsResource("npm/prettier/plugins/php.clean"); } } From bc9410b69745141b201c034ccc6a6a1411f288e6 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Fri, 12 Jun 2020 11:30:22 +0200 Subject: [PATCH 40/43] adapting test for new output format --- .../spotless/maven/prettier/PrettierFormatStepTest.java | 2 +- .../com/diffplug/spotless/npm/PrettierFormatterStepTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java index a0b80baa1c..971c04a343 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java @@ -19,13 +19,13 @@ import java.io.IOException; -import com.diffplug.spotless.maven.generic.Prettier; import org.junit.Test; import org.junit.experimental.categories.Category; import com.diffplug.spotless.category.NpmTest; import com.diffplug.spotless.maven.MavenIntegrationHarness; import com.diffplug.spotless.maven.MavenRunner; +import com.diffplug.spotless.maven.generic.Prettier; @Category(NpmTest.class) public class PrettierFormatStepTest extends MavenIntegrationHarness { diff --git a/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java b/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java index 039451c1c9..bb4ca9710f 100644 --- a/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java @@ -97,7 +97,7 @@ public void recreate500InternalServerError() throws Exception { new PrettierConfig(null, ImmutableMap.of("parser", "postcss"))); try (StepHarness stepHarness = StepHarness.forStep(formatterStep)) { stepHarness.testException("npm/prettier/filetypes/scss/causes500error.dirty", exception -> { - exception.hasMessageStartingWith("500: Internal Server Error"); + exception.hasMessageContaining("HTTP 501"); }); } } From 38053bce859803023627dfbf3b4c508489b4799a Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Fri, 12 Jun 2020 11:49:00 +0200 Subject: [PATCH 41/43] and verify what we actually learned: error message needs to be relayed correctly --- .../filetypes/scss/causes500error.dirty | 94 ------------------- .../npm/PrettierFormatterStepTest.java | 5 +- 2 files changed, 3 insertions(+), 96 deletions(-) delete mode 100644 testlib/src/main/resources/npm/prettier/filetypes/scss/causes500error.dirty diff --git a/testlib/src/main/resources/npm/prettier/filetypes/scss/causes500error.dirty b/testlib/src/main/resources/npm/prettier/filetypes/scss/causes500error.dirty deleted file mode 100644 index 330cf4b5c9..0000000000 --- a/testlib/src/main/resources/npm/prettier/filetypes/scss/causes500error.dirty +++ /dev/null @@ -1,94 +0,0 @@ -///////////////// -// OPEN SOURCE // -///////////////// -#opensource-heading { - @extend .btn-raised; - background-color: white; - padding: 10px; - border-radius: $border-radius-base; - > h3 { - margin: 0; - } - img { - width: 500px; - max-width: 100%; - float: left; - } - ul { - list-style-position: inside; - > li > span { - position: relative; - left: -1em; - } - } -} -#opensource { - $animate-time: 500ms; - $min-width: 300px; - .category { - color: white; - display: block; - background-color: $blue-light; - text-transform: uppercase; - text-align: center; - - &:hover { - background-color: lighten($blue-light, 10%); - cursor: pointer; - text-decoration: none; - } - } - - > #opensource-categories { - margin: 15px 0; - > .category { - @extend .btn-raised; - width: 120px; - float: left; - font-size: 20px; - padding: 5px 10px; - margin: 0 10px 10px 0; - } - } - - > .opensource-lib { - @include transition(all $transition-time ease-in-out); - @extend .btn-raised; - float: left; - width: $min-width; - height: 100px; - padding: 5px; - margin: 0 10px 10px 0; - background-color: white; - color: black; - &:hover { - text-decoration: none; - } - img { - max-height: 90px; - max-width: 90px; - float: left; - } - > h3 { - padding: 0; - margin: 0; - line-height: $font-size-h3; - display: inline; - } - > .categories { - float: right; - font-size: 13px; - width: 60px; - //margin: 2px 0; - padding: 0; - > .category { - width: 100%; - padding: 2px 5px; - margin: 0 0 5px 0; - } - } - > .description { - margin: 0; - } - } -} diff --git a/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java b/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java index bb4ca9710f..fa15b186ad 100644 --- a/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/npm/PrettierFormatterStepTest.java @@ -88,7 +88,7 @@ public void parserInferenceIsWorking() throws Exception { } @Test - public void recreate500InternalServerError() throws Exception { + public void verifyPrettierErrorMessageIsRelayed() throws Exception { FormatterStep formatterStep = PrettierFormatterStep.create( PrettierFormatterStep.defaultDevDependenciesWithPrettier("2.0.5"), TestProvisioner.mavenCentral(), @@ -96,8 +96,9 @@ public void recreate500InternalServerError() throws Exception { npmExecutable(), new PrettierConfig(null, ImmutableMap.of("parser", "postcss"))); try (StepHarness stepHarness = StepHarness.forStep(formatterStep)) { - stepHarness.testException("npm/prettier/filetypes/scss/causes500error.dirty", exception -> { + stepHarness.testException("npm/prettier/filetypes/scss/scss.dirty", exception -> { exception.hasMessageContaining("HTTP 501"); + exception.hasMessageContaining("Couldn't resolve parser \"postcss\""); }); } } From 08d83218db00e267a1a56674b4946b554054ea83 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 12 Jun 2020 15:43:13 -0700 Subject: [PATCH 42/43] Update changes. --- CHANGES.md | 3 ++- plugin-gradle/CHANGES.md | 5 +++++ plugin-maven/CHANGES.md | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 4ee55c4d60..169012c595 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,9 +11,10 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Changed -* Nodejs-based formatters `prettier` and `tsfmt` now use native node instead of the J2V8 approach. (([#606](https://github.com/diffplug/spotless/pull/606))) +* Nodejs-based formatters `prettier` and `tsfmt` now use native node instead of the J2V8 approach. ([#606](https://github.com/diffplug/spotless/pull/606)) * This removes the dependency to the no-longer-maintained Linux/Windows/macOs variants of J2V8. * This enables spotless to use the latest `prettier` versions (instead of being stuck at prettier version <= `1.19.0`) + * Bumped default versions, prettier `1.16.4` -> `2.0.5`, tslint `5.12.1` -> `6.1.2` ## [1.34.0] - 2020-06-05 diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index b1823ccd0f..c1dc8d12e8 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -3,6 +3,11 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`). ## [Unreleased] +### Changed +* Nodejs-based formatters `prettier` and `tsfmt` now use native node instead of the J2V8 approach. ([#606](https://github.com/diffplug/spotless/pull/606)) + * This removes the dependency to the no-longer-maintained Linux/Windows/macOs variants of J2V8. + * This enables spotless to use the latest `prettier` versions (instead of being stuck at prettier version <= `1.19.0`) + * Bumped default versions, prettier `1.16.4` -> `2.0.5`, tslint `5.12.1` -> `6.1.2` ## [4.3.0] - 2020-06-05 ### Deprecated diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index e2874d243b..258013a93b 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] +### Changed +* Nodejs-based formatters `prettier` and `tsfmt` now use native node instead of the J2V8 approach. ([#606](https://github.com/diffplug/spotless/pull/606)) + * This removes the dependency to the no-longer-maintained Linux/Windows/macOs variants of J2V8. + * This enables spotless to use the latest `prettier` versions (instead of being stuck at prettier version <= `1.19.0`) + * Bumped default versions, prettier `1.16.4` -> `2.0.5`, tslint `5.12.1` -> `6.1.2` ### Fixed * `licenseHeader` is now more robust when parsing years from existing license headers. ([#593](https://github.com/diffplug/spotless/pull/593)) From a69670e74ed8777286ec76652235ffea7baa90b3 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 12 Jun 2020 15:46:49 -0700 Subject: [PATCH 43/43] Remove some empty newlines. --- plugin-gradle/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index f77262c6b0..d5acee50c6 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -443,9 +443,7 @@ It is also possible to specify the config via file: spotless { format 'styling', { target 'src/*/webapp/**/*.css', 'src/*/webapp/**/*.scss', 'app/**/*.css', 'app/**/*.scss' - prettier().configFile('/path-to/.prettierrc.yml') - // or provide both (config options take precedence over configFile options) prettier().config(['parser': 'css']).configFile('path-to/.prettierrc.yml') } @@ -478,7 +476,6 @@ Since spotless uses the actual npm prettier package behind the scenes, it is pos spotless { format 'prettierJava', { target 'src/*/java/**/*.java' - prettier(['prettier': '2.0.5', 'prettier-plugin-java': '0.8.0']).config(['parser': 'java', 'tabWidth': 4]) } format 'php', {