diff --git a/CHANGES.md b/CHANGES.md
index 3d334e7658..05c07860d9 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -10,6 +10,8 @@ This document is intended for Spotless developers.
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).
## [Unreleased]
+### Added
+* Added support for custom JSR223 formatters ([#945](https://github.com/diffplug/spotless/pull/945))
## [2.17.0] - 2021-09-27
### Added
diff --git a/README.md b/README.md
index 3854ba7d69..5085cee272 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,7 @@ output = [
'| Fast format on fresh checkout using buildcache | {{yes}} | {{no}} | {{no}} | {{no}} |',
lib('generic.EndWithNewlineStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('generic.IndentStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
+lib('generic.Jsr223Step') +'{{no}} | {{yes}} | {{no}} | {{no}} |',
lib('generic.LicenseHeaderStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
lib('generic.NativeCmdStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |',
lib('generic.ReplaceRegexStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
@@ -83,6 +84,7 @@ extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}}
| Fast format on fresh checkout using buildcache | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
| [`generic.EndWithNewlineStep`](lib/src/main/java/com/diffplug/spotless/generic/EndWithNewlineStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`generic.IndentStep`](lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
+| [`generic.Jsr223Step`](lib/src/main/java/com/diffplug/spotless/generic/Jsr223Step.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: |
| [`generic.LicenseHeaderStep`](lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
| [`generic.NativeCmdStep`](lib/src/main/java/com/diffplug/spotless/generic/NativeCmdStep.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: |
| [`generic.ReplaceRegexStep`](lib/src/main/java/com/diffplug/spotless/generic/ReplaceRegexStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
diff --git a/lib/src/main/java/com/diffplug/spotless/generic/Jsr223Step.java b/lib/src/main/java/com/diffplug/spotless/generic/Jsr223Step.java
new file mode 100644
index 0000000000..90f57b24ef
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/generic/Jsr223Step.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2021 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.generic;
+
+import java.io.Serializable;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+
+import com.diffplug.spotless.FormatterFunc;
+import com.diffplug.spotless.FormatterStep;
+import com.diffplug.spotless.JarState;
+import com.diffplug.spotless.Provisioner;
+
+public final class Jsr223Step {
+ // prevent direct instantiation
+ private Jsr223Step() {}
+
+ public static FormatterStep create(String name, String dependency, CharSequence engine, CharSequence script, Provisioner provisioner) {
+ Objects.requireNonNull(name, "name");
+ Objects.requireNonNull(engine, "engine");
+ Objects.requireNonNull(script, "script");
+ return FormatterStep.createLazy(name,
+ () -> new State(dependency == null ? null : JarState.from(dependency, provisioner), engine, script),
+ State::toFormatter);
+ }
+
+ private static final class State implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private final JarState jarState;
+ private final String engine;
+ private final String script;
+
+ State(JarState jarState, CharSequence engine, CharSequence script) {
+ this.jarState = jarState;
+ this.engine = engine.toString();
+ this.script = script.toString();
+ }
+
+ FormatterFunc toFormatter() {
+ ScriptEngineManager scriptEngineManager;
+ if (jarState == null) {
+ scriptEngineManager = new ScriptEngineManager(ClassLoader.getSystemClassLoader());
+ } else {
+ scriptEngineManager = new ScriptEngineManager(jarState.getClassLoader());
+ }
+ ScriptEngine scriptEngine = scriptEngineManager.getEngineByName(engine);
+
+ if (scriptEngine == null) {
+ throw new IllegalArgumentException("Unknown script engine '" + engine + "'. Available engines: " +
+ scriptEngineManager.getEngineFactories().stream().flatMap(f -> f.getNames().stream()).collect(Collectors.joining(", ")));
+ }
+
+ // evaluate script code
+ return raw -> {
+ scriptEngine.put("source", raw);
+ return (String) scriptEngine.eval(script);
+ };
+ }
+ }
+}
diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md
index 2549cb8e19..7b9aa00a55 100644
--- a/plugin-maven/CHANGES.md
+++ b/plugin-maven/CHANGES.md
@@ -3,6 +3,8 @@
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).
## [Unreleased]
+### Added
+* Added support for custom JSR223 formatters ([#945](https://github.com/diffplug/spotless/pull/945))
## [2.14.0] - 2021-09-27
### Added
diff --git a/plugin-maven/README.md b/plugin-maven/README.md
index c2f8f0d630..0136504299 100644
--- a/plugin-maven/README.md
+++ b/plugin-maven/README.md
@@ -769,6 +769,13 @@ to true.
4
+
+ Greetings to Mars
+ org.codehaus.groovy:groovy-jsr223:3.0.9
+ groovy
+
+
+
Greetings to Mars from sed
/usr/bin/sed
diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java
index d90063188d..e15c987ffa 100644
--- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java
+++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java
@@ -110,6 +110,10 @@ public final void addIndent(Indent indent) {
addStepFactory(indent);
}
+ public final void addJsr223(Jsr223 jsr223) {
+ addStepFactory(jsr223);
+ }
+
public final void addTrimTrailingWhitespace(TrimTrailingWhitespace trimTrailingWhitespace) {
addStepFactory(trimTrailingWhitespace);
}
diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Jsr223.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Jsr223.java
new file mode 100644
index 0000000000..151b490112
--- /dev/null
+++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Jsr223.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.maven.generic;
+
+import org.apache.maven.plugins.annotations.Parameter;
+
+import com.diffplug.spotless.FormatterStep;
+import com.diffplug.spotless.generic.Jsr223Step;
+import com.diffplug.spotless.maven.FormatterStepConfig;
+import com.diffplug.spotless.maven.FormatterStepFactory;
+
+public class Jsr223 implements FormatterStepFactory {
+
+ @Parameter
+ private String name;
+
+ @Parameter
+ private String dependency;
+
+ @Parameter
+ private String engine;
+
+ @Parameter
+ private String script;
+
+ @Override
+ public FormatterStep newFormatterStep(FormatterStepConfig config) {
+ if (name == null || engine == null || script == null) {
+ throw new IllegalArgumentException("Must specify 'name', 'engine' and 'script'.");
+ }
+
+ return Jsr223Step.create(name, dependency, engine, script, config.getProvisioner());
+ }
+}
diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/generic/Jsr223Test.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/generic/Jsr223Test.java
new file mode 100644
index 0000000000..eb1b3b0575
--- /dev/null
+++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/generic/Jsr223Test.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.maven.generic;
+
+import static org.assertj.core.api.Assumptions.assumeThat;
+
+import javax.script.ScriptEngineManager;
+
+import org.junit.jupiter.api.Test;
+
+import com.diffplug.spotless.maven.MavenIntegrationHarness;
+
+public class Jsr223Test extends MavenIntegrationHarness {
+
+ @Test
+ public void buildInNashorn() throws Exception {
+ // This will only work for JDKs that bundle nashorn (8-14)
+ assumeThat(new ScriptEngineManager().getEngineByName("nashorn")).isNotNull();
+ writePomWithFormatSteps(
+ "",
+ " Greetings to Mars",
+ " nashorn",
+ " ",
+ "");
+ runTest("Hello World", "Hello Mars");
+ }
+
+ @Test
+ public void groovyFromJarState() throws Exception {
+ writePomWithFormatSteps(
+ "",
+ " Greetings to Mars",
+ " org.codehaus.groovy:groovy-jsr223:3.0.9",
+ " groovy",
+ " ",
+ "");
+ runTest("Hello World", "Hello Mars");
+ }
+
+ private void runTest(String sourceContent, String targetContent) throws Exception {
+ String path = "src/main/java/test.java";
+ setFile(path).toContent(sourceContent);
+ mavenRunner().withArguments("spotless:apply").runNoError();
+ assertFile(path).hasContent(targetContent);
+ }
+}